ホームページ >システムチュートリアル >Linux >以前のプログラマーが残したコードに対処する方法
これは退屈で気の遠くなるような作業になる可能性がありますが、他の開発者が書いたコードを使用できる柔軟性があるため、到達範囲の拡大やソフトウェアの破損の修正など、大きなメリットを得ることができます。私たちがこれまで知らなかったことを学びました(言うまでもなく、他のプログラマーのテクニックやテクニックを学ぶこともできます)。
他の開発者が書いたコードの使用には利点だけでなく煩わしさもあるため、重大な間違いを犯さないように注意する必要があります。
私たちも含めて開発者は人間であるため、他の開発者が作成したコードを扱う場合、人間性の問題の多くに対処することが役立ちます。この記事では、人間の性質の理解を確実に活用し、既存のコードとオリジナルの作成者から可能な限り多くの助けを借り、他の開発者が書いたコードを有効にするために使用できる 5 つのテクニックを説明します。前に。ここに挙げた 5 つの方法は包括的なものではありませんが、以下の手法を使用すると、他の開発者が作成したコードを変更することになった場合でも、新しい機能と既存の機能の互換性を確保しながら、既存の機能を確実に動作し続けることができます。コードベースは調和されています。
1. テストの存在を確認する唯一の本当の自信の源は、他の開発者によって書かれたコードに存在する既存の機能が実際に期待どおりに動作すること、そしてそれに加えられた変更がその機能に影響を与えないことを保証することです。唯一の解決策は、コードをサポートすることです。テスト。別の開発者によって作成されたコードに遭遇した場合、そのコードは 2 つの状態になる可能性があります。(1) 十分なテスト レベルがない、または (2) 十分なテスト レベルがある。前者の場合、私たちはテストを作成する責任がありますが、後者の場合、既存のテストを使用して、加えた変更がコードを壊さず、テストから可能な限り多くのことを活用できるようにすることができます。コード。
新しいテストの作成これは悲しい例です。他の開発者のコードを変更するとき、私たちは変更の結果に対して責任を負いますが、変更を加えたときに何も壊さないことを保証する方法はありません。文句を言っても無駄だ。コードがどのような状態で見つかったとしても、私たちはコードに触れなければならないので、コードが壊れた場合は私たちの責任です。したがって、コードを変更するときは、自分自身の動作を制御する必要があります。コードを壊さないようにする唯一の方法は、自分でテストを書くことです。
これは面倒ですが、テストを書くことで学習できるので、これが主な利点です。コードが正しく動作するようになり、予期される入力が予期される出力につながるようにテストを作成する必要があるとします。このテストに取り組むにつれて、コードの意図と機能について徐々に学習していきます。たとえば、次のコードがあるとします。 リーリー
コードの意図やコード内でマジック ナンバーが使用されている理由についてはよくわかりませんが、既知の入力を使用して既知の出力を生成する一連のテストを作成することはできます。たとえば、簡単な計算を行って、成功を構成する給与のしきい値の問題を解くと、30 歳未満で年間約 68,330 ドルを稼いでいれば、その人は (この仕様の標準に従って) 成功しているとみなされることがわかります。これらの魔法の数字が何であるかはわかりませんが、初任給の値が減少することはわかっています。したがって、$68,330 の基準値は、控除前の基本給です。この情報を使用すると、次のような簡単なテストを作成できます。public class SuccessfulFilterTest { private static final double THRESHOLD_NET_SALARY = 68330.0; @Test public void under30AndNettingThresholdEnsureSuccessful() { Person person = new Person(29, THRESHOLD_NET_SALARY); Assert.assertTrue(new SuccessfulFilter().test(person)); } @Test public void exactly30AndNettingThresholdEnsureUnsuccessful() { Person person = new Person(30, THRESHOLD_NET_SALARY); Assert.assertFalse(new SuccessfulFilter().test(person)); } @Test public void under30AndNettingLessThanThresholdEnsureSuccessful() { Person person = new Person(29, THRESHOLD_NET_SALARY - 1); Assert.assertFalse(new SuccessfulFilter().test(person)); } }
通过这三个测试,我们现在对现有代码的工作方式有了大致的了解:如果一个人不到30岁,且每年赚$ 68,300,那么他被认为是成功人士。虽然我们可以创建更多的测试来确保临界情况(例如空白年龄或工资)功能正常,但是一些简短的测试不仅使我们了解了原始功能,还给出了一套自动化测试,可用于确保在对现有代码进行更改时,我们不会破坏现有功能。
使用现有测试如果有足够的代码测试组件,那么我们可以从测试中学到很多东西。正如我们创建测试一样,通过阅读测试,我们可以了解代码如何在功能层面上工作。此外,我们还可以知道原作者是如何让代码运行的。即使测试是由原作者以外的人(在我们接触之前)撰写的,也依然能够为我们提供关于其他人对代码的看法。
虽然现有的测试可以提供帮助,但我们仍然需要对此持保留态度。测试是否与代码的开发更改一起与时俱进是很难说的。如果是的话,那么这是一个很好的理解基础;如果不是,那么我们要小心不要被误导。例如,如果初始的工资阈值是每年75,000美元,而后来更改为我们的68,330美元,那么下面这个过时的测试可能会使我们误入歧途:
@Test public void under30AndNettingThresholdEnsureSuccessful() { Person person = new Person(29, 75000.0); Assert.assertTrue(new SuccessfulFilter().test(person)); }
这个测试还是会通过的,但没有了预期的作用。通过的原因不是因为它正好是阈值,而是因为它超出了阈值。如果此测试组件包含这样一个测试用例:当薪水低于阈值1美元时,过滤器就返回false,这样第二个测试将会失败,表明阈值是错误的。如果套件没有这样的测试,那么陈旧的数据会很容易误导我们弄错代码的真正意图。当有疑问时,请相信代码:正如我们之前所表述的那样,求解阈值表明测试没有对准实际阈值。
另外,要查看代码和测试用例的存储库日志(即Git日志):如果代码的最后更新日期比测试的最后更新日期更近(对代码进行了重大更改,例如更改阈值),则测试可能已经过时,应谨慎查看。注意,我们不应该完全忽视测试,因为它们也许仍然能为我们提供关于原作者(或最近撰写测试的开发人员)意图的一些文档,但它们可能包含过时或不正确的数据。
2.与编写代码的人交流在涉及多个人的任何工作中,沟通至关重要。无论是企业,越野旅行还是软件项目,缺乏沟通是损害任务最有效的手段之一。即使我们在创建新代码时进行沟通,但是当我们接触现有的代码时,风险会增加。因为此时我们对现有的代码并不太了解,因此我们所了解的内容可能是被误导的,或只代表了其中的一小部分。为了真正了解现有的代码,我们需要和编写它的人交流。
当开始提出问题时,我们需要确定问题是具体的,并且旨在实现我们理解代码的目标。例如:
始终要保持谦虚的态度,积极寻求原作者真正的答案。几乎每个开发人员都碰到过这样的场景,他或她看着别人的代码,自问自答:“为什么他/她要这样做?为什么他们不这样做?”然后花几个小时来得出本来只要原作者回答就能得到的结论。大多数开发人员都是有才华的程序员,所以即使如果我们遇到一个看似糟糕的决定,也有可能有一个很好的理由(可能没有,但研究别人的代码时最好假设他们这样做是有原因的;如果真的没有,我们可以通过重构来改变)。
沟通在软件开发中起次要副作用。1967年最初由Melvin Conway创立的康威定律规定:
设计系统的任何组织…都将不可避免地产生一种设计,该设计结构反映了组织的通信结构。
これは、大規模で緊密にコミュニケーションされたチームは統合された密結合のコードを生成する可能性があるが、一部の小規模チームはより独立した疎結合のコードを生成する可能性があることを意味します (この相関関係の詳細については、「コンウェイの法則を読み解く」を参照してください)。私たちにとって、これは、通信構造がコードの特定の部分だけでなく、コードベース全体に影響を与えることを意味します。したがって、原作者とのコミュニケーションを密にすることは確かに良い方法ですが、原作者に頼りすぎないように自分自身をチェックする必要があります。これは元の作成者を悩ませる可能性があるだけでなく、コード内に意図しない結合が生じる可能性もあります。
これはコードをより深く掘り下げるのに役立ちますが、これは元の作成者にアクセスできることを前提としています。多くの場合、原作者は会社を辞めているか、たまたま休暇中などで会社を離れている可能性があります。この状況では何をすべきでしょうか?コードについて何か知っている人に質問してください。この人は実際にコードに取り組んでいる必要はなく、元の作成者がコードを書いたときにその場にいた可能性もあれば、元の作成者を知っている可能性もあります。元の開発者の周囲の人々からのほんの少しの言葉でも、他の未知のコード スニペットに啓発される可能性があります。
3. すべての警告を削除します心理学には「割れ窓理論」と呼ばれるよく知られた概念があり、これについては Andrew Hunt と Dave Thomas が『The Pragmatic Programmer』 (4 ~ 6 ページ) で詳しく説明しています。この理論はもともと James Q. Wilson と George L. Kelling によって提案されたもので、次のように説明されています。
窓がいくつか壊れている建物があるとします。窓が修理されていない場合、破壊者はさらに多くの窓を割る傾向があります。最終的には、建物に侵入したり、不法占拠したり、建物に人がいない場合には内部で火災を起こしたりする可能性もあります。歩道の状況も考慮してください。道路にゴミが溜まると、すぐにさらにゴミが溜まってしまいます。最終的には、持ち帰ったゴミをそこに捨てたり、車を壊したりする人さえ出てくるでしょう。 この理論は、誰もその物や物を気にしていないようであれば、その物や物の世話を無視することになる、これが人間の本性であると述べています。たとえば、建物がすでに乱雑に見える場合、破壊される可能性が高くなります。ソフトウェアに関して言えば、この理論は、開発者がコードが混乱していることに気付いた場合、それを壊すのが人間の性質であることを意味します。本質的に、私たちが考えていることは(精神活動がそれほど豊かではないとしても)、「最後の人がこのコードを気にしないなら、なぜ私が気にする必要があるだろうか?」または「ただのめちゃくちゃなコードだ、誰が書いたのか誰にも分からない」ということです。それ。" ###しかし、これを言い訳にしてはいけません。以前に他の人が所有していたコードに触れるたびに、私たちはそのコードに対して責任を負い、それが効果的に機能しない場合にはその結果に苦しみます。この人間の行動に打ち勝つには、コードが汚れる頻度を減らすための小さな措置を講じる必要があります (壊れた窓をすぐに交換するなど)。
簡単な方法は、使用しているパッケージまたはモジュール全体からすべての警告を削除することです。未使用のコードやコメント化されたコードは削除してください。コードのこの部分が後で必要になった場合は、リポジトリでいつでも前のコミットから取得できます。直接解決できない警告 (プリミティブ型の警告など) がある場合は、呼び出しまたはメソッドに @SuppressWarnings アノテーションを付けます。これにより、コードについて注意深く検討したことが保証されます。これらは見落としによる警告ではなく、明示的に気づいた警告 (プリミティブ型など) です。
すべての警告を削除または明示的に抑制したら、コードに警告がないことを確認する必要があります。これには 2 つの主な効果があります:
作成するコードについて慎重に考える必要があります。
4.再建
過去数十年にわたり、リファクタリングは非常に重要な用語になり、最近では現在動作しているコードに変更を加えるという同義語として使用されています。リファクタリングには現在作業しているコードへの変更が含まれますが、それが全体像ではありません。 Martin Fowler は、このテーマに関する重要な著書『リファクタリング』の中で、リファクタリングを次のように定義しています。 ソフトウェアの目に見える動作を変えることなく、ソフトウェアの内部構造を変更して、理解しやすく、より安価に変更できるようにします。
这个定义的关键在于它涉及的更改不会改变系统可观察的行为。这意味着当我们重构代码时,我们必须要有方法来确保代码的外部可见行为不会改变。在我们的例子中,这意味着是在我们继承或自己开发的测试套件中。为了确保我们没有改变系统的外部行为,每当我们进行改变时,都必须重新编译和执行我们的全部测试。
此外,并不是我们所做的每一个改变都被认为是重构。例如,重命名方法以更好地反映其预期用途是重构,但添加新功能不是。为了看到重构的好处,我们将重构SuccessfulFilter。执行的第一个重构是提取方法,以更好地封装个人净工资的逻辑:
public class SuccessfulFilter implements Predicate { @Override public boolean test(Person person) { return person.getAge() 60000; } private double getNetSalary(Person person) { return (((person.getSalary() - (250 * 12)) - 1500) * 0.94); } }
在我们进行这种改变之后,我们重新编译并运行我们的测试套件,测试套件将继续通过。现在更容易看出,成功是通过一个人的年龄和净薪酬定义的,但是getNetSalary方法似乎并不像Person类一样属于SuccessfulFilter(指示标志就是该方法的唯一参数是Person,该方法的唯一调用是Person类的方法,因此对Person类有很强的亲和力)。 为了更好地定位这个方法,我们执行一个Move方法将其移动到Person类:
public class Person { private int age; private double salary; public Person(int age, double salary) { this.age = age; this.salary = salary; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void setSalary(double salary) { this.salary = salary; } public double getSalary() { return salary; } public double getNetSalary() { return ((getSalary() - (250 * 12)) - 1500) * 0.94; } } public class SuccessfulFilter implements Predicate { @Override public boolean test(Person person) { return person.getAge() 60000; } }
为了进一步清理此代码,我们对每个magic number执行符号常量替换magic number行为。为了知道这些值的含义,我们可能得和原作者交流,或者向具有足够领域知识的人请教,以引领正确的方向。我们还将执行更多的提取方法重构,以确保现有的方法尽可能简单。
public class Person { private static final int MONTHLY_BONUS = 250; private static final int YEARLY_BONUS = MONTHLY_BONUS * 12; private static final int YEARLY_BENEFITS_DEDUCTIONS = 1500; private static final double YEARLY_401K_CONTRIBUTION_PERCENT = 0.06; private static final double YEARLY_401K_CONTRIBUTION_MUTLIPLIER = 1 - YEARLY_401K_CONTRIBUTION_PERCENT; private int age; private double salary; public Person(int age, double salary) { this.age = age; this.salary = salary; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void setSalary(double salary) { this.salary = salary; } public double getSalary() { return salary; } public double getNetSalary() { return getPostDeductionSalary(); } private double getPostDeductionSalary() { return getPostBenefitsSalary() * YEARLY_401K_CONTRIBUTION_MUTLIPLIER; } private double getPostBenefitsSalary() { return getSalary() - YEARLY_BONUS - YEARLY_BENEFITS_DEDUCTIONS; } } public class SuccessfulFilter implements Predicate { private static final int THRESHOLD_AGE = 30; private static final double THRESHOLD_SALARY = 60000.0; @Override public boolean test(Person person) { return person.getAge() THRESHOLD_SALARY; } }
重新编译和测试,发现系统仍然按照预期的方式工作:我们没有改变外部行为,但是我们改进了代码的可靠性和内部结构。有关更复杂的重构和重构过程,请参阅Martin Fowler的Refactoring Guru网站。
5.当你离开的时候,代码比你发现它的时候更好最后这个技术在概念上非常简单,但在实践中很困难:让代码比你发现它的时候更好。当我们梳理代码,特别是别人的代码时,我们大多会添加功能,测试它,然后前行,不关心我们会不会贡献软件腐烂,也不在乎我们添加到类的新方法会不会导致额外的混乱。因此,本文的全部内容可总结为以下规则:
每当我们修改代码时,请确保当你离开的时候,代码比你发现它的时候更好。
前面提到过,我们需要对类造成的损坏和对改变的代码负责,如果它不能工作,那么修复是我们的职责。为了战胜伴随软件生产而出现的熵,我们必须强制自己做到离开时的代码比我们发现它的时候更佳。为了不逃避这个问题,我们必须偿还技术债务,确保下一个接触代码的人不需要再付出代价。说不定,将来可能是我们自己感谢自己这个时候的坚持呢。
以上が以前のプログラマーが残したコードに対処する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。