数据库代码编写_在旧版代码库中编写干净的代码

数据库代码编写

There’s a false assumption about software that makes people think of a trade-off between quality and speed. Most people believe that we could drop quality to gain development speed. This argument is commonly held between management and development teams, and the saddest part is that most of the time, developers agree to this unrealistic trade-off.

关于软件的错误假设使人们想到质量和速度之间的权衡。 大多数人认为,我们可以降低质量来提高发展速度。 这种论点通常在管理团队和开发团队之间持有,最可悲的是,在大多数情况下,开发人员都同意这种不切实际的权衡。

There are many arguments to change this impression. But, in my opinion, the fact that low-quality software is more costly than high-quality software just after a few weeks, and the results observed in studies such as the State of DevOps Report are two ways to start the discussion. The following paragraphs will try to show just that.

有很多论据可以改变这种印象。 但是,我认为,几周后,劣质软件比高质量软件更昂贵的事实以及在诸如《 DevOps报告》之类的研究中观察到的结果是展开讨论的两种方法。 以下段落将试图证明这一点。

As software developers, we know that with time the complexity of our software increases. But there are two types of complexity, the first one is inherent to the problem domain we are addressing, and the second one is the unintentional complexity added as a side effect of implementing features in our codebase. This second type of complexity is usually referred to as either technical debt or cruft.

作为软件开发人员,我们知道随着时间的流逝,我们软件的复杂性会增加。 但是有两种类型的复杂性,第一种是我们要解决的问题领域所固有的,第二种是作为在代码库中实现功能的副作用而添加的无意识的复杂性。 第二种类型的复杂性通常称为技术债务杂项。

The accumulation of technical debt increases the complexity of our software, and, since creating a new feature always require to start by understanding the current implementation, there’s an additional cost that needs to be paid just to begin writing code. Technical debt has various consequences but in this context, as suggested by Martin Fowler in is high quality software worth the cost? two are important to mention. First, it makes it harder to understand how to make a change. And second, it increases the chance to make mistakes (introduce bugs into production). Both of these facts lead to the same result, a slow down in the speed to create software.

技术债务的积累增加了我们软件的复杂性,并且由于创建新功能总是需要从了解当前的实现入手,因此刚开始编写代码需要支付额外的费用。 技术债务有各种后果,但是在这种情况下,正如马丁·福勒(Martin Fowler)所建议的那样, 高质量的软件是否值得? 提到两个很重要。 首先,它使您很难理解如何进行更改。 其次,它增加了犯错的机会(将错误引入生产中)。 这两个事实导致相同的结果,从而降低了创建软件的速度。

As it should sound logical now, the only way to avoid the slow down in software development is to keep cruft to a minimum, so that teams can add new features with less effort. This fact is clearly stated in the State of DevOps Report, where the results show that elite teams that apply good software practices are capable of deploying software multiple times per day, while in contrast, low performing teams take months to deploy a change.

听起来似乎合乎逻辑,避免软件开发速度减慢的唯一方法是将工作量降到最低,以便团队可以轻松地添加新功能。 《 DevOps状态报告》清楚地表明了这一事实,该结果表明,采用良好软件实践的精英团队每天可以多次部署软件,而性能低下的团队则需要数月才能部署变更。

I adhere to the thought that clean code is an important way to reduce the amount of technical debt in a codebase, and the rest of this article will try to exemplify that.

我坚持认为干净的代码是减少代码库中技术债务数量的重要方法,本文的其余部分将以此为例。

为什么干净的代码很重要? (Why is clean code important?)

The definition of clean code is somewhat imprecise and difficult. But, it’s rather easy to pin point bad code. You know when you are dealing with code that has lots of tangled dependencies and when changing something will break something else in an unexpected place (fragility).

干净代码的定义有些不精确且困难。 但是,找出错误的代码很容易。 您知道当您处理具有很多纠结的依赖关系的代码时,以及何时进行更改会在意外的地方破坏其他内容(易碎性)。

In this article I refer to clean code as the programming good practices that make code more readable, flexible, and maintainable.

在本文中,我将干净的代码称为使代码更具可读性,灵活性和可维护性的编程良好实践

但是,代码“干净”意味着什么? (But, what does it mean for code to be “clean”?)

To solve this question I am going to introduce a small example.

为了解决这个问题,我将介绍一个小例子。

A bowling alley hires you to build their system, and you are tasked to start by implementing the scoring module.

保龄球馆雇用您来构建他们的系统,而您的任务是开始实施计分模块。

Have you ever bowled before? (If you haven’t, take a look at this Wikipedia entry)

你以前打过保龄球吗? (如果您还没有,请查看此Wikipedia条目)

As the problem seems really trivial, you start coding right away:

由于这个问题似乎很简单,因此您可以立即开始编码:

This implementation works as expected, and the customers and your boss are happy because the tedious scorecards are part of history now.

此实现按预期工作,并且客户和您的老板很高兴,因为乏味的记分卡已成为历史的一部分。

After a few months and due to the success of the bowling alley, your boss decides that he wants to make a small change to the scoring system. He wants to give 2 additional points every time you complete three strikes.

几个月后,由于保龄球馆的成功,您的老板决定要对计分系统进行一些更改。 每当您完成三击时,他想再给2分。

You think this seems trivial, and start hacking your way into your old code.

您认为这似乎是微不足道的,然后开始破解旧代码。

public int score() {
    int out = 0;
    int str = 0;
    for (int i = 0; i < 10; i++) {
      if (allfrme[0][i] == 10) {
        str ++;
        if (i == 9) {
          out += 10 + add;
        } else {
          if (allfrme[0][i + 1] == 10 && i < 8) {
            out += 10 + allfrme[0][i + 1] + allfrme[0][i + 2];
          } else if (allfrme[0][i + 1] == 10 && i == 8) {
            out += 10 + allfrme[0][i + 1] + add2;
          } else {
            out += 10 + allfrme[0][i + 1] + allfrme[1][i + 1];
          }
        }
        out += str % 3 == 0 ? 2 : 0;
      } else if (allfrme[0][i] + allfrme[1][i] == 10) {
        if (i == 9) {
          out += 10 + add;
        } else {
          out += 10 + allfrme[0][i + 1];
        }
      } else {
        out += allfrme[0][i] + allfrme[1][i];
      }
    }
    return out;
  }

By adding the str variable and doing two statements with it (and increment and using it to increase the out variable), you got what you wanted. After running some manual tests on your code a couple of times, you feel satisfied with the results.

通过添加str变量并对其执行两个语句(并递增并使用它来增加out变量),您将获得所需的内容。 在代码上运行了几次手动测试后,您对结果感到满意。

A couple of months go by, and after more success with the changes, your boss decides that he now wants to add another change:

几个月后,变更成功之后,您的老板决定他现在要添加另一个变更:

  • He wants to have a variable number of frames (not just 10 as in the basic game)

    他希望拥有可变数量的帧(不仅仅是基本游戏中的10帧)

When you go back to check the code, you realize you are in trouble:

当您返回检查代码时,您发现自己遇到了麻烦:

  • The variables’ names don’t mean anything

    变量的名称没有任何意义
  • There’s a lot of nested if statements

    有很多嵌套的if语句
  • Lots of 10 appear on the code, and you don’t remember which ones refer to the number of frames and which to the number of pins

    代码上出现很多10 ,并且您忘记了哪个是指帧数,哪个是指引脚数

Making changes now is risky. How can you now test that by adding the frames functionality you won’t break anything?

现在进行更改是有风险的。 现在,您如何通过添加框架功能进行测试,不会破坏任何内容?

Moreover, since the first change was so fast, the bowling alley already advertised the change for next week.

而且,由于第一个更改如此之快,保龄球馆已经在下周宣传了该更改。

After this, you must decide between two alternatives:

之后,您必须在两种选择之间做出选择:

  1. Attempting to understand the code, and modify it to add the new functionality to the game while running lots of manual tests, or

    尝试理解代码,并进行修改以在游戏中添加新功能,同时运行大量手动测试,或者
  2. Add some unit tests to make sure you won’t break things. And also refactor the code to make it more readable and attempt the changes.

    添加一些单元测试以确保您不会出错。 并重构代码以使其更具可读性并尝试进行更改。

如何在旧版代码库中创建干净的代码 (How to create clean code in a legacy codebase)

The previous example illustrates the value of clean code to allow fast changes in a codebase. But when we are applying changes for software that has already been in production, we usually have to deal with difficulties such as the lack of knowledge of the expected behavior, lack of documentation, bad code practices, and all sorts of complexities. To this type of code, we usually refer to as Legacy Code.

前面的示例说明了允许在代码库中进行快速更改的纯净代码的价值。 但是,当我们对已经投入生产的软件进行更改时,我们通常必须面对一些困难,例如缺乏对预期行为的了解,缺乏文档,不良的代码惯例以及各种复杂性。 对于这种类型的代码,我们通常将其称为“ 旧版代码”

I’m one of the people who have a hard time believing that the word legacy has a bad connotation. How can it be that our legacy is something people are going to complain about? — As uncle Bob puts it in his article code hoaders this usually happens because we put more effort into adding new features(regardless of how they are implemented) than organizing existing ones.

我是很难相信“ 遗产 ”一词含义不好的人之一。 人们如何会抱怨我们的遗产 ? —正如Bob叔叔在他的文章代码存储库中所说的那样,这通常是发生的,因为与组织现有功能相比,我们投入更多的精力来添加新功能(无论如何实现)。

But if you’re already dealing with this situation, I think you can decide to do one of the following two things:

但是,如果您已经在处理这种情况,我认为您可以决定执行以下两项操作之一:

  1. Keep adding complexity, applying poor practices and increasing the bad connotation of the word Legacy, or

    不断增加复杂性,应用不良做法并增加“传统”一词的不良含义,或者
  2. Improve the existing codebase and be proud of your legacy (your code) in that project

    改善现有的代码库,并以该项目中的旧版(您的代码)为荣

If you want to do the second thing this is my advice to you:

如果您想做第二件事,这是我对您的建议:

有反馈的工作 (Work having feedback)

As Michael Feathers puts it in his book Working effectively with legacy code, there are two ways to make changes in a system:

正如迈克尔·费瑟斯(Michael Feathers)在他的书中所言, 有效地使用遗留代码 ,可以通过两种方法对系统进行更改:

  1. Edit and Pray, or

    编辑并祈祷,或者
  2. Cover and Modify

    覆盖并修改

Edit and Pray is much like working with care. That means, analyzing the existing code and trying to apply the changes as carefully as possible. But, working with care is not good enough if you don’t apply the right tools and techniques.

编辑和祈祷很像谨慎工作。 这意味着,分析现有代码并尝试尽可能仔细地应用更改。 但是,如果您未使用正确的工具和技术,那么谨慎地工作还不够好。

Cover and Modify is completely different. The central point is that you work with a safety net while modifying code. This safety net means that we must cover the code we are modifying with tests. This way, we can have fast feedback about the effect of our changes. There are different types of tests, but if we want agile feedback, we need tests that run fast and that you can run frequently (run it, every time you make a significant change, this must be less than a few minutes apart). This takes as to the concept of Unit Tests (basically, tests that run fast and help us localize errors).

Cover和Modify完全不同。 重点是您在修改代码时使用安全网 。 这个安全网意味着我们必须涵盖通过测试修改的代码 。 这样,我们可以快速获得有关更改效果的反馈。 有不同类型的测试,但是如果我们需要敏捷的反馈,则需要能够快速运行并且可以频繁运行的测试(运行它,每次进行重大更改时,它们之间的间隔必须少于几分钟)。 这涉及单元测试的概念(基本上是运行速度快并有助于我们定位错误的测试)。

The problem is that if our code doesn’t have tests, we must add them. But, to add tests you usually have to modify code. This is referred to as The Legacy Code Dilemma, and the solution is to perform safe minimal refactoring that allows us to add tests.

问题是,如果我们的代码没有测试,则必须添加它们。 但是,要添加测试,通常必须修改代码。 这被称为“遗留代码难题”,解决方案是执行安全的最小重构,从而允许我们添加测试。

Adding tests can be a nightmare! Most of the time, the existing code was not written to be testable in the first place (and yes! testability is another characteristic you must have in your systems).

添加测试可能是一场噩梦! 在大多数情况下,现有代码最初并不是被编写为可测试的(是的,可测试性是系统中必须具备的另一个特性)

This rigidity usually comes from having hard dependencies on other objects. This can come in different flavors, but database connections are a common example.

这种刚性通常来自对其他对象的严格依赖。 这可能有不同的风格,但是数据库连接是一个常见的示例。

class UntestableBusinessLogic {


  private PostgresRepository postgresRepository = new PostgresRepository();


  public void doSomething() {
    // some logic ...
    postgresRepository.doSomething();
    // more logic ...
  }


}

This makes it very hard to mock the behavior of the repository and forces us to have a connection to Postgres every time we want to test something.

这使得模拟存储库的行为变得非常困难,并且每次我们要测试某些东西时都迫使我们与Postgres建立连接。

查找要插入更改的点 (Finding points to insert changes)

Michael Feathers defines a seam as a place to alter program behavior, without changing the code.

Michael Feathers将接缝定义为无需更改代码即可更改程序行为的地方。

By introducing an Interface, and receiving the repository as an argument to the constructor, we open the place to alter the behavior of our BusinessLogic object. This simple change creates a seam in our code. It allows the creator of the object to choose what type of Repository should be used. This would allow us to test our code without using a database connection.

通过引入一个接口,并接收存储库作为构造函数的参数,我们打开了改变BusinessLogic对象行为的地方。 这个简单的更改在我们的代码中创建了一个接缝 。 它允许对象的创建者选择应使用哪种类型的存储库。 这将允许我们在不使用数据库连接的情况下测试代码。

class BusinessLogic {


    private Repository repository;


    public BusinessLogic(Repository repository) {
      this.repository = repository;
    }


    public void doSomething() {
      repository.doSomething();
    }


  }
  
interface Repository {
  void doSomething();
}


class MockRepository implements Repository {


  public void doSomething() {
    // mocked behavior
  }
}


class PostgresRepository implements Repository {
  public void doSomething() {
    // real postgres
  }
}

急于进行更改 (Making changes on a hurry)

In an ideal world, whenever you have to deal with existing code that doesn’t have tests, you would start by adding them. But there are some situations when business pressure and deadlines are really tight, and spending a couple of days to test existing code is simply not possible. What to do then?

在理想的世界中,每当您必须处理没有测试的现有代码时,就从添加它们开始。 但是在某些情况下,业务压力和截止日期确实很紧,根本不可能花费几天的时间来测试现有代码。 那该怎么办?

As suggested by Feathers, you could use the sprout technique:

根据Feathers的建议,您可以使用发芽技术:

  • Identify where you need to make your code change — find the insertion point.

    确定更改代码所需的位置-找到插入点。
  • Create a method call to your new functionality. Get a sense of what the final result will be. This new method will be your sprout.

    创建对新功能的方法调用。 了解最终结果。 这种新方法将使您发芽

  • Create your code somewhere else and add tests to it (preferably use TDD)

    在其他地方创建代码并添加测试(最好使用TDD)

To show this technique, I will use Feathers’ example.

为了展示这种技术,我将使用Feathers的示例。

public class TransactionGate {


    private TransactionBundle transactionBundle;


    public void postEntries(List entries) {
      for (Iterator it = entries.iterator(); it.hasNext(); ) {
        Entry entry = (Entry)it.next();
        entry.postDate();
      }
      // ... very complex logic ...
      transactionBundle.getListManager().add(entries);
    }
    // ...
  }

Suppose you need to remove the duplication of entries before calling postDate(), but testing postEntries() is hard.

假设您需要在调用postDate()之前删除条目的重复项,但是测试postEntries()很难。

You could follow the sprout technique by creating a new method findUniqueEntries(). This new method can be fully tested and cleanly developed.

您可以通过创建新方法findUniqueEntries()来遵循发芽技术。 这种新方法可以经过全面测试和彻底开发。

public class TransactionGate {


    private TransactionBundle transactionBundle;


    public void postEntries(List entries) {
      List uniqueEntries = findUniqueEntries(entries);


      for (Iterator it = uniqueEntries.iterator(); it.hasNext(); ) {
        Entry entry = (Entry)it.next();
        entry.postDate();
      }
      // ... very complex logic ...
      transactionBundle.getListManager().add(entries);
    }


    // your brand new sprout method! - should be covered with tests
    private List findUniqueEntries(List entries) {
      // your clever and tested clean code here!!
    }
    // ...
  }

Feathers’ book has a lot more to offer in terms of strategies to deal with specific situations you’ll find when dealing with legacy code. I highly recommend anyone interested in the topic to take a look at it.

Feathers的书在处理遗留代码时遇到的特定情况的策略方面提供了更多内容。 我强烈建议对这个主题感兴趣的人看看。

将功能标记作为另一个安全网 (Feature flags as another safety net)

Feature flags (also known as feature toggles) are another tool that can help to avoid problems when facing legacy code. They allow modifying the system behavior without changing code. Feature flagging allows developers to wrap features in an if/then statement to gain control over its release.

功能标志(也称为功能切换)是另一个工具,可以帮助您避免在面对旧代码时出现问题。 它们允许在不更改代码的情况下修改系统行为。 功能标记允许开发人员将功能包装在if/then语句中,以控制其发布。

By wrapping a feature with a flag, it’s possible to isolate its effect on the system and to turn that flag on or off independent from a deployment. Flagging gives another safety net to the newly developed code, by separating rollout from deployment.

通过使用标志包装功能,可以隔离其对系统的影响,并可以独立于部署而打开或关闭该标志。 标记通过将部署与部署分开,为新开发的代码提供了另一个安全网。

void someFunction() {
    if (featureFlag) {
      doNewStuff();
    } else {
      doOldStuff();
    }
  }

Flags seem easy to understand. But their complexity level can vary widely depending on the level of control you want to accomplish with them:

标志似乎很容易理解。 但是,它们的复杂程度可能会有所不同,具体取决于您要对它们执行的控制级别:

  • You could change their value for specific sets of users allowing you to accomplish canary releasing.

    您可以为特定的用户组更改其值,以完成金丝雀释放。
  • You could manually control the value for specific hours and verify the results (do experiments).

    您可以手动控制特定小时的值并验证结果(进行实验)。
  • You could use them to control the permissions of specific users.

    您可以使用它们来控制特定用户的权限。
  • You could use them to control the operational aspects of the platform.

    您可以使用它们来控制平台的操作方面。

For the sake of our discussion, the important point here is that you can add another safety net after releasing your code by making smart use of feature toggles in your code.

为了便于讨论,此处的重点是,您可以在代码发布后通过巧妙利用代码中的功能开关来添加另一个安全网。

将保龄球馆更改为更清洁的版本 (Changing the Bowling Alley to a Cleaner Version)

用测试覆盖我们的代码 (Covering our code with tests)

We will start by creating our safety net before modifying the production code. This way, we can have fast feedback about the effect of our changes.

我们将首先创建安全网,然后再修改生产代码。 这样,我们可以快速获得有关更改效果的反馈。

For our Bowling Alley, the following Unit Tests cover:

对于我们的保龄球馆,以下单元测试包括:

  • Normal game (including strikes and spares).

    正常比赛(包括罢工和备件)。
  • An additional bonus of two points every three strikes.

    每三击可获得2分的额外奖励。
public class BonusBowlingAlleyTest {


  private static final int MAX_ROLLS_NO_BONUS_LAST_FRAME = 20;
  private DirtyBonusBowlingAlley bowlingAlley;


  @Before
  public void setup() {
    bowlingAlley = new DirtyBonusBowlingAlley();
  }


  @Test
  public void gutterGame() {


    knockNumberOfPinTimes(0, MAX_ROLLS_NO_BONUS_LAST_FRAME);
    assertEquals(0, bowlingAlley.score());
  }


  @Test
  public void allOnes() {
    knockNumberOfPinTimes(1, MAX_ROLLS_NO_BONUS_LAST_FRAME);


    assertEquals(20, bowlingAlley.score());
  }


  @Test
  public void twoOnesAndThenTwos() {
    knockNumberOfPinTimes(1, 2);
    knockNumberOfPinTimes(2, 18);


    assertEquals(2 + 2*18, bowlingAlley.score());
  }


  @Test
  public void spareAndThenOnes() {
    rollSpare();
    knockNumberOfPinTimes(1, 18);


    assertEquals(11 + 18, bowlingAlley.score());
  }


  @Test
  public void degenerateSpareInTheMiddle() {
    knockNumberOfPinTimes(3, 6);
    rollSpareWithFirstPin(7);
    knockNumberOfPinTimes(4, 12);


    assertEquals(3 * 6 + 14 + 48, bowlingAlley.score());
  }


  @Test
  public void spareInLastFrame() {
    knockNumberOfPinTimes(2, 18);
    rollSpareWithFirstPin(8);
    knockNumberOfPinTimes(5, 1);


    assertEquals(36 + 15, bowlingAlley.score());
  }


  @Test
  public void strikeInFirstFrame() {
    rollStrike();
    knockNumberOfPinTimes(2, 18);


    assertEquals(14 + 36, bowlingAlley.score());
  }


  @Test
  public void strikeInMiddleFrame() {
    knockNumberOfPinTimes(4, 4);
    rollStrike();
    knockNumberOfPinTimes(4, 14);


    assertEquals(90, bowlingAlley.score());
  }


  @Test
  public void strikeInLast() {
    knockNumberOfPinTimes(4, 18);
    rollStrike();
    knockNumberOfPinTimes(4, 2);


    assertEquals(4 * 18 + 18, bowlingAlley.score());
  }


  @Test
  public void threeStrikesGame() {
    rollThreeStrikes();
    knockNumberOfPinTimes(2, 14);


    assertEquals(96, bowlingAlley.score());
  }


  @Test
  public void sixStrikesGame() {
    rollThreeStrikes();
    knockNumberOfPinTimes(2, 4);
    rollThreeStrikes();
    knockNumberOfPinTimes(2, 4);


    assertEquals((30 + 22 + 14 + 2 + 2 * 4) * 2, bowlingAlley.score());
  }


  @Test
  public void perfectGame() {
    for (int i = 0; i < 12; i++) {
      rollStrike();
    }


    assertEquals(306, bowlingAlley.score());
  }


  @Test
  public void thirdStrikeInLastGame() {
    knockNumberOfPinTimes(1, 14);
    rollThreeStrikes();
    knockNumberOfPinTimes(2, 2);


    assertEquals(14 + 30 + 22 + 16, bowlingAlley.score());
  }


  private void rollThreeStrikes() {
    rollStrike();
    rollStrike();
    rollStrike();
  }


  private void rollStrike() {
    bowlingAlley.roll(10);
  }


  private void rollSpare() {
    rollSpareWithFirstPin(3);
  }


  private void rollSpareWithFirstPin(int firstPin) {
    bowlingAlley.roll(firstPin);
    bowlingAlley.roll(10 - firstPin);
  }


  private void knockNumberOfPinTimes(int pins, int times) {
    for (int i = 0; i < times; i++) {
      bowlingAlley.roll(pins);
    }
  }


}

重构干净代码 (Refactoring to clean code)

Now, with our safety net in place, we can start refactoring our existing code, to make it easier to follow. Among the changes shown below you can find:

现在,有了我们的安全网,我们就可以开始重构我们现有的代码,以使其易于遵循。 在下面显示的更改中,您可以找到:

  • Variables were renamed to something easier to read

    变量被重命名为易于阅读的名称
  • Indentation level was reduced (fewer levels inside methods)

    压痕级别降低(方法内部的级别更少)
  • Specific validations were taken to their own methods

    具体的验证采用他们自己的方法
public class NormalBowlingAlley {


  private static final int NUMBER_OF_FRAMES = 10;


  private int roll = 0;
  private int[] pinsGame = new int[NUMBER_OF_FRAMES * 2 + 1];


  @Override
  public void roll(int knockedPins) {
    pinsGame[roll] = knockedPins;
    roll++;
  }


  @Override
  public int score() {
    int result = 0;
    int currentRoll = 0;
    int strikes = 0;
    for (int frame = 0; frame < NUMBER_OF_FRAMES; frame++) {
      if (isAStrike(currentRoll)) {
        strikes ++;
        int bonus = strikes % 3 == 0 ? 1 : 0;
        result += calcBonusScore(currentRoll) + bonus;
        currentRoll += 1;
      } else if (isSpare(currentRoll)) {
        result += calcBonusScore(currentRoll);
        currentRoll += 2;
      } else {
        result += calcFrameScore(currentRoll);
        currentRoll += 2;
      }
    }
    return result;
  }


  private boolean isAStrike(int roll) {
    return pinsGame[roll] == 10;
  }


  private boolean isSpare(int roll) {
    return calcFrameScore(roll) == 10;
  }


  private int calcFrameScore(int currentRoll) {
    return pinsGame[currentRoll] + pinsGame[currentRoll + 1];
  }


  private int calcBonusScore(int currentRoll) {
    return pinsGame[currentRoll] + pinsGame[currentRoll + 1] + pinsGame[currentRoll + 2];
  }
}

But, if passing around arguments among methods bothers you, or if having two indentation levels does, you can go even further:

但是,如果在方法之间传递参数会令您感到困扰,或者如果有两个缩进级别,那么您可以走得更远:

public class NormalBowlingAlley {


  private static final int NUMBER_OF_FRAMES = 10;


  private int[] pinsGame = new int[NUMBER_OF_FRAMES * 2 + 1];
  private int roll = 0;


  @Override
  public void roll(int knockedPins) {
    pinsGame[roll] = knockedPins;
    roll++;
  }


  @Override
  public int score() {
    return new ScoreCalculator(pinsGame).score();
  }


  private static class ScoreCalculator {
    private final int[] pinsGame;
    private int roll = 0;
    private int numberOfStrikes = 0;
    private int score = 0;


    public ScoreCalculator(int[] pinsGame) {
      this.pinsGame = pinsGame;
    }


    int score() {
      for (int frame = 0; frame < NUMBER_OF_FRAMES; frame++) {
        processFrame();
      }
      return score;
    }


    private void processFrame() {
      if (isStrike()) {
        processStrike();
      } else if (isSpare()) {
        processSpare();
      } else {
        processRoll();
      }
    }


    private boolean isStrike() {
      return pinsGame[roll] == 10;
    }


    private void processStrike() {
      numberOfStrikes ++;
      score += calcStrikeScore();
      roll += 1;
    }


    private int calcStrikeScore() {
      int threeStrikesBonus = numberOfStrikes % 3 == 0 ? 2 : 0;
      return calcBonusScore() + threeStrikesBonus;
    }


    private boolean isSpare() {
      return calcFrameScore() == 10;
    }


    private void processSpare() {
      score += calcBonusScore();
      roll += 2;
    }


    private int calcBonusScore() {
      return pinsGame[roll] + pinsGame[roll + 1] + pinsGame[roll + 2];
    }


    private void processRoll() {
      score += calcFrameScore();
      roll += 2;
    }


    private int calcFrameScore() {
      return pinsGame[roll] + pinsGame[roll + 1];
    }


  }
}

在我们的代码中添加新功能 (Adding the new functionality to our code)

Remember, we want to have a variable number of frames (received as input for our class).

记住,我们希望有可变数量的帧(作为班级的输入被接收)。

With our new code, the change is trivial, we just have to replace our static NUMBER_OF_FRAMES, for a constructor argument:

使用我们的新代码,更改是微不足道的,我们只需要将静态NUMBER_OF_FRAMES替换为构造函数参数即可:

public class FramesBowlingAlley {


	// variables as before
	private final int numberOfFrames; // new variable
	
	public FramesBowlingAlley(int numberOfFrames) {
		this.numberOfFrames = numberOfFrames;
		pinsGame = new int[numberOfFrames * 2 + 1];
	}
	
	// roll as before
	
	// score modification
	public int score() {
		return new ScoreCalculator(pinsGame, numberOfFrames).score();
	}


	private static class ScoreCalculator {
		
		private final int numberOfFrames; // new variable
		// variables as before


		public ScoreCalculator(int[] pinsGame, int numberOfFrames) {
			this.pinsGame = pinsGame;
			this.numberOfFrames = numberOfFrames;
		}


		int score() {
			for (int frame = 0; frame < numberOfFrames; frame++) { //only code change
				processFrame();
			}
			return score;
		}
  
 		// the rest as before
	}
}

For the sake of simplicity, I am not showing the new unit tests added to verify the new functionality (but if this was real, they should be there! … do not open the door to untested functionality).

为了简单起见,我没有展示为验证新功能而添加的新单元测试(但是,如果这是真实的,则应该存在!……不要为未经测试的功能敞开大门)。

This demonstrates how easy it was to perform improvements when you have a clean codebase to work on, and how a safety net can help you deal with the uncertainty of making changes.

这证明了在拥有干净的代码库时进行改进很容易,并且安全网可以如何帮助您应对进行更改的不确定性。

As a final remark, remember: your code will be your legacy at some point. Be proud of the legacy you leave behind by using clean code.

最后,请记住:在某些时候,您的代码将成为您的遗产。 使用干净的代码为您留下的遗产感到自豪。

进一步阅读 (Further Reading)

Several books and articles have lots of useful information to teach you navigate legacy code, but out of those, two that have helped me to write clean code even in legacy codebases, those books are:

几本书和文章中有很多有用的信息可以教您如何导航遗留代码,但是其中有两本书甚至帮助我在遗留代码库中编写了清晰的代码,其中包括:

  • Clean Code by Robert C. Martin

    罗伯特·马丁(Robert C.Martin)的清洁代码
  • Working Effectively with Legacy Code by Michael Feathers

    通过迈克尔·费瑟斯有效地使用遗留代码

There are many other resources you could use. Among them, the book “Refactoring: Improving the Design of Existing Code” by Martin Fowler is another one worth mentioning.

您可以使用许多其他资源。 其中值得一提的是马丁·福勒(Martin Fowler)的著作《重构:改进现有代码的设计》。

翻译自: https://medium.com/payulatam-engineering/writing-clean-code-in-a-legacy-codebase-7a256de5c5e

数据库代码编写

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值