Bien que cela puisse être une tâche fastidieuse et ardue, en raison de la flexibilité d'utiliser du code écrit par d'autres développeurs, nous pouvons en tirer des avantages significatifs, notamment en augmentant notre portée, en corrigeant la pourriture des logiciels et en apprenant des parties du système que nous n'avons pas connues. Je n'ai pas compris avant (sans parler de l'apprentissage des techniques et des techniques d'autres programmeurs).
Étant donné que l'utilisation de code écrit par d'autres développeurs présente des inconvénients et des avantages, nous devons faire attention à ne pas commettre de graves erreurs :
Étant donné que les développeurs, nous y compris, sont humains, il est utile de traiter de nombreux problèmes liés à la nature humaine lorsque l'on travaille avec du code écrit par d'autres développeurs. Dans cet article, nous passerons en revue cinq techniques que nous pouvons utiliser pour garantir que la compréhension de la nature humaine est utilisée à notre avantage, tirer autant d'aide que possible du code existant et des auteurs originaux, et activer le code écrit par d'autres développeurs. ça devient mieux qu'avant. Bien que les 5 méthodes répertoriées ici ne soient pas exhaustives, l'utilisation des techniques ci-dessous garantira que lorsque nous finirons par modifier le code écrit par d'autres développeurs, nous sommes convaincus que nous maintiendrons les fonctionnalités existantes fonctionnelles tout en garantissant que nos nouvelles fonctionnalités sont compatibles avec celles existantes. La base de code est harmonisée.
1. Assurez-vous que le test existeLa seule véritable confiance est de garantir que les fonctionnalités existantes qui existent dans le code écrit par d'autres développeurs fonctionnent réellement comme prévu et que les modifications que nous y apportons n'affecteront pas la fonctionnalité. La façon d'y parvenir est de prendre en charge le code avec des tests. Lorsque nous rencontrons du code écrit par un autre développeur, le code peut être dans deux états : (1) le niveau de test n'est pas suffisant, ou (2) le niveau de test est suffisant. Dans le premier cas, nous sommes responsables de la création des tests, tandis que dans le second cas, nous pouvons utiliser les tests existants pour garantir que les modifications que nous apportons ne cassent pas le code et tirent le meilleur parti possible des tests. Comprendre l'intention de le code.
Créer un nouveau testC'est un triste exemple : lorsque nous modifions le code d'autres développeurs, nous sommes responsables des résultats du changement, mais nous n'avons aucun moyen de garantir que nous ne cassons rien lorsque nous effectuons le changement. Il ne sert à rien de se plaindre. Peu importe l'état dans lequel nous trouvons le code, nous devons toucher le code, donc si le code se brise, c'est notre responsabilité. Ainsi, lorsque nous modifions le code, nous devons contrôler notre propre comportement. La seule façon d’être sûr de ne pas casser le code est d’écrire les tests vous-même.
Bien que cela soit fastidieux, cela nous permet d'apprendre en écrivant des tests, ce qui est son principal avantage. Supposons que le code fonctionne désormais correctement et que nous devons écrire des tests pour que les entrées attendues conduisent aux sorties attendues. Au fur et à mesure que nous effectuons ce test, nous découvrons progressivement l’intention et la fonctionnalité du code. Par exemple, étant donné le code suivant
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 class SuccessfulFilter implements Predicate { @Override public boolean test(Person person) { return person.getAge() 60000); } }
Nous ne savons pas grand-chose sur l'intention du code et pourquoi des nombres magiques sont utilisés dans le code, mais nous pouvons créer un ensemble de tests dans lesquels les entrées connues produisent des sorties connues. Par exemple, en faisant quelques calculs simples et en résolvant le problème du seuil de salaire qui constitue le succès, nous constatons qu'une personne est considérée comme ayant réussi (selon les normes de cette spécification) si elle a moins de 30 ans et gagne environ 68 330 $ par an. Même si nous ne savons pas quels sont ces chiffres magiques, nous savons qu’ils réduisent la valeur initiale du salaire. Par conséquent, le seuil de 68 330 $ correspond au salaire de base avant déductions. En utilisant ces informations, nous pouvons créer des tests simples tels que :
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创立的康威定律规定:
设计系统的任何组织…都将不可避免地产生一种设计,该设计结构反映了组织的通信结构。
Cela signifie qu'une grande équipe qui communique étroitement peut produire du code intégré et étroitement couplé, mais que certaines équipes plus petites peuvent produire du code plus indépendant et faiblement couplé (pour plus d'informations sur cette corrélation, veuillez consulter « Démystifier la loi de Conway »). Pour nous, cela signifie que notre structure de communication n’affecte pas seulement des morceaux de code spécifiques, mais l’ensemble de la base de code. Par conséquent, une communication étroite avec l’auteur original est certainement un bon moyen, mais nous devons nous assurer de ne pas trop nous fier à l’auteur original. Non seulement cela risque de gêner l’auteur original, mais cela peut également créer un couplage involontaire dans notre code.
Bien que cela nous aide à approfondir le code, cela suppose l'accès à l'auteur original. Dans de nombreux cas, l'auteur original peut avoir quitté l'entreprise ou se trouver absent de l'entreprise (par exemple en vacances). Que devons-nous faire dans cette situation ? Demandez à toute personne susceptible de connaître quelque chose sur le code. Il n'est pas nécessaire que cette personne ait réellement travaillé sur le code ; elle aurait pu être présente lorsque l'auteur original a écrit le code, ou elle pourrait connaître l'auteur original. Même quelques mots de personnes autour du développeur d'origine peuvent éclairer d'autres extraits de code inconnus.
3. Supprimez tous les avertissementsIl existe un concept bien connu en psychologie appelé « théorie des fenêtres brisées », qui est décrit en détail par Andrew Hunt et Dave Thomas dans « The Pragmatic Programmer » (pages 4-6). Cette théorie a été initialement proposée par James Q. Wilson et George L. Kelling et est décrite comme suit :
Supposons qu'il y ait un bâtiment avec plusieurs fenêtres cassées. Si les fenêtres ne sont pas réparées, les vandales auront tendance à briser davantage de fenêtres. À terme, ils peuvent même pénétrer par effraction dans le bâtiment, l'occuper illégalement ou déclencher un incendie à l'intérieur si le bâtiment est inoccupé. Tenez également compte de l’état des trottoirs. Si les déchets s’accumulent sur la route, d’autres déchets s’accumuleront bientôt. À terme, les gens commenceront même à y jeter des déchets à emporter et même à casser des voitures.
Cette théorie stipule que si personne ne semble se soucier de l'objet ou de la chose, alors nous ignorerons l'entretien de l'objet ou de la chose. C'est la nature humaine. Par exemple, si un bâtiment semble déjà en désordre, il est plus susceptible d'être vandalisé. En termes de logiciel, cette théorie signifie que si un développeur découvre que le code est en désordre, il est dans la nature humaine de le casser. Essentiellement, ce que nous pensons (même si l'activité mentale n'est pas si riche) est : « Si la dernière personne ne se soucie pas de ce code, pourquoi devrais-je m'en soucier ou « C'est juste du code foiré, qui sait qui l'a écrit ? ça."
Cependant, cela ne devrait pas être notre excuse. Chaque fois que nous touchons du code qui appartenait auparavant à quelqu'un d'autre, nous sommes responsables de ce code et subissons les conséquences s'il ne fonctionne pas efficacement. Pour vaincre ce comportement humain, nous devons prendre de petites mesures pour éviter que notre code ne se salisse moins souvent (en remplaçant rapidement les fenêtres cassées).
Un moyen simple consiste à supprimer tous les avertissements de l'ensemble du package ou du module que nous utilisons. Quant au code inutilisé ou commenté, supprimez-le. Si nous avons besoin de cette partie du code plus tard, dans le référentiel, nous pouvons toujours la récupérer à partir d'un commit précédent. S'il existe des avertissements qui ne peuvent pas être résolus directement (tels que les avertissements de type primitif), annotez l'appel ou la méthode avec l'annotation @SuppressWarnings. Cela garantit que nous avons soigneusement réfléchi à notre code : ce ne sont pas des avertissements dus à un oubli, mais plutôt des avertissements que nous avons explicitement remarqués (comme les types primitifs).
Une fois que nous avons supprimé ou supprimé explicitement tous les avertissements, nous devons alors nous assurer que le code reste exempt d'avertissements. Cela a deux effets principaux :
Cela a une implication psychologique pour les autres, ainsi que pour nous-mêmes : nous nous soucions réellement du code sur lequel nous travaillons. Ce n'est plus une voie à sens unique : nous nous forçons à changer le code, à le respecter et à ne jamais regarder en arrière. Au lieu de cela, nous reconnaissons que nous devons assumer la responsabilité du code. Ceci est également utile pour le développement futur de logiciels - cela montre aux futurs développeurs qu'il ne s'agit pas d'un entrepôt avec des fenêtres cassées : c'est une base de code bien entretenue.
4.RefactorRefactoring est devenu un terme très important au cours des dernières décennies et a récemment été utilisé comme synonyme pour apporter des modifications au code actuellement fonctionnel. Bien que la refactorisation implique des modifications du code sur lequel vous travaillez actuellement, elle ne donne pas une vue d'ensemble. Martin Fowler, dans son livre fondateur sur le sujet, Refactoring, définit le refactoring comme :
Apporter des modifications à la structure interne du logiciel pour le rendre plus facile à comprendre et moins coûteux à modifier sans modifier son comportement observable.
这个定义的关键在于它涉及的更改不会改变系统可观察的行为。这意味着当我们重构代码时,我们必须要有方法来确保代码的外部可见行为不会改变。在我们的例子中,这意味着是在我们继承或自己开发的测试套件中。为了确保我们没有改变系统的外部行为,每当我们进行改变时,都必须重新编译和执行我们的全部测试。
此外,并不是我们所做的每一个改变都被认为是重构。例如,重命名方法以更好地反映其预期用途是重构,但添加新功能不是。为了看到重构的好处,我们将重构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.当你离开的时候,代码比你发现它的时候更好最后这个技术在概念上非常简单,但在实践中很困难:让代码比你发现它的时候更好。当我们梳理代码,特别是别人的代码时,我们大多会添加功能,测试它,然后前行,不关心我们会不会贡献软件腐烂,也不在乎我们添加到类的新方法会不会导致额外的混乱。因此,本文的全部内容可总结为以下规则:
每当我们修改代码时,请确保当你离开的时候,代码比你发现它的时候更好。
前面提到过,我们需要对类造成的损坏和对改变的代码负责,如果它不能工作,那么修复是我们的职责。为了战胜伴随软件生产而出现的熵,我们必须强制自己做到离开时的代码比我们发现它的时候更佳。为了不逃避这个问题,我们必须偿还技术债务,确保下一个接触代码的人不需要再付出代价。说不定,将来可能是我们自己感谢自己这个时候的坚持呢。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!