>  기사  >  시스템 튜토리얼  >  이전 프로그래머가 남긴 코드를 처리하는 방법

이전 프로그래머가 남긴 코드를 처리하는 방법

王林
王林앞으로
2024-01-19 10:36:161060검색

이 작업은 지루하고 힘든 작업일 수 있지만 다른 개발자가 작성한 코드를 사용할 수 있는 유연성으로 인해 도달 범위 확대, 소프트웨어 부패 수정, 시스템에서 우리가 하지 못한 부분 학습 등 상당한 이점을 얻을 수 있습니다. 이전에는 이해하지 못했습니다 (다른 프로그래머의 기술과 기술을 배우는 것은 말할 것도 없습니다).

다른 개발자가 작성한 코드를 사용하는 것은 귀찮고 장점이 있기 때문에 심각한 실수를 저지르지 않도록 주의해야 합니다.

  • 우리의 자기 인식: 우리는 자신이 가장 잘 안다고 느낄 수도 있지만 종종 그렇지 않습니다. 우리가 바꾸고 있는 것은 우리가 거의 알지 못하는 코드입니다. 즉, 원저작자의 의도, 이 코드를 작성하게 된 결정, 원저자가 코드를 작성할 때 사용할 수 있는 도구 및 프레임워크 등을 알지 못합니다. 겸손의 질은 수천 달러의 가치가 있습니다. 당신은 그럴 자격이 있습니다.
  • 원저자의 자기 인식: 우리가 지금 다루려는 코드는 스타일, 제약, 기한, 개인 생활(업무 외 시간을 소비하는)을 가진 다른 개발자가 작성한 것입니다. 우리가 자신이 내리는 결정에 의문을 제기하거나 코드가 왜 그렇게 불결한지 의문을 제기하기 시작할 때만 그 사람은 자신을 반성하고 오만함을 멈춥니다. 우리는 원저자가 우리 작업을 방해하지 않고 도움을 줄 수 있도록 모든 노력을 기울여야 합니다.
  • 미지에 대한 두려움: 우리가 접하게 될 코드는 우리가 거의 또는 전혀 알지 못하는 코드일 때가 많습니다. 무서운 부분은 다음과 같습니다. 우리는 우리가 만드는 모든 변화에 대해 책임을 지게 되지만, 기본적으로 우리는 빛도 없는 어두운 방을 돌아다니고 있습니다. 걱정하기보다는 크고 작은 변화에 편안함을 느낄 수 있고, 기존 기능을 망가뜨리지 않을 수 있는 구조를 구축해야 합니다.

우리 자신을 포함한 개발자는 인간이므로 다른 개발자가 작성한 코드로 작업할 때 인간 본성 문제를 많이 다루는 것이 유용합니다. 이 게시물에서는 인간 본성에 대한 이해를 유리하게 활용하고, 기존 코드와 원본 작성자로부터 최대한 많은 도움을 받고, 다른 개발자가 작성한 코드를 활용하는 데 사용할 수 있는 5가지 기술을 살펴보겠습니다. 결국 전보다 좋아진다. 여기에 나열된 5가지 방법이 포괄적이지는 않지만 아래 기술을 사용하면 다른 개발자가 작성한 코드를 변경할 때 새로운 기능이 기존 기능과 일관성을 유지하면서 기존 기능이 계속 작동할 것이라고 확신합니다. 코드 베이스가 조화를 이룹니다.

이전 프로그래머가 남긴 코드를 처리하는 방법

1. 테스트가 있는지 확인하세요

진정한 확신은 다른 개발자가 작성한 코드에 존재하는 기존 기능이 실제로 예상대로 작동하는지 확인하고 이에 대한 변경 사항이 기능에 영향을 미치지 않는다는 것입니다. 이를 수행하는 방법은 테스트를 통해 코드를 지원하는 것입니다. 다른 개발자가 작성한 코드를 접할 때 코드는 두 가지 상태일 수 있습니다. (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创立的康威定律规定:

设计系统的任何组织…都将不可避免地产生一种设计,该设计结构反映了组织的通信结构。

이는 긴밀하게 소통된 대규모 팀이 통합되고 긴밀하게 결합된 코드를 생성할 수 있지만 일부 소규모 팀은 보다 독립적이고 느슨하게 결합된 코드를 생성할 수 있음을 의미합니다(이 상관관계에 대한 자세한 내용은 "Demystifying Conway's Law"를 참조하십시오). 우리에게 이것은 우리의 통신 구조가 특정 코드 조각뿐만 아니라 전체 코드 기반에 영향을 미친다는 것을 의미합니다. 그러므로 원저자와의 긴밀한 소통은 분명 좋은 방법이지만, 원저자에게 너무 의존하지 않도록 스스로 점검해야 할 것입니다. 이는 원저작자를 짜증나게 할 뿐만 아니라 코드에 의도하지 않은 결합을 일으킬 수도 있습니다.

이는 코드를 더 깊이 파고드는 데 도움이 되지만 원본 작성자에 대한 액세스를 가정합니다. 많은 경우, 원저자가 회사를 떠났거나, 우연히 회사를 떠나게 되었을 수도 있습니다(예를 들어 휴가 중). 이런 상황에서 우리는 어떻게 해야 합니까? 코드에 대해 알고 있는 사람에게 물어보세요. 이 사람은 실제로 코드 작업을 했을 필요는 없습니다. 원래 작성자가 코드를 작성할 당시 주변에 있었거나 원래 작성자를 알고 있었을 수도 있습니다. 원래 개발자와 가까운 사람들의 몇 마디 말만으로도 다른 알려지지 않은 코드 조각을 알 수 있습니다.

3. 모든 경고를 제거하세요

심리학에는 "깨진 유리창 이론"이라는 잘 알려진 개념이 있는데, Andrew Hunt와 Dave Thomas가 "The Pragmatic Programmer"(4-6페이지)에서 자세히 설명합니다. 이 이론은 원래 James Q. Wilson과 George L. Kelling이 제안했으며 다음과 같이 설명됩니다.

창문이 여러 개 깨진 건물이 있다고 가정해 보세요. 창문을 수리하지 않으면 파손자가 더 많은 창문을 깨뜨리는 경향이 있습니다. 결국 그들은 침입하여 건물을 불법적으로 점거하거나, 건물이 비어 있으면 내부에 불을 지를 수도 있습니다. 보도의 상태도 고려하십시오. 도로에 쓰레기가 쌓이면 곧 더 많은 쓰레기가 쌓일 것입니다. 결국 사람들은 그곳에 테이크아웃 쓰레기를 버리고 심지어 자동차를 부수기 시작할 것입니다.

이 이론은 아무도 그 물건이나 물건에 관심을 두지 않는 것처럼 보이면 우리는 그 물건이나 물건에 대한 관리, 즉 인간의 본성을 무시할 것이라고 말합니다. 예를 들어, 건물이 이미 어수선해 보인다면 파손될 가능성이 더 높습니다. 소프트웨어 측면에서 이 이론은 개발자가 코드가 엉망이라는 것을 발견하면 이를 깨뜨리는 것이 인간의 본성임을 의미합니다. 본질적으로 우리가 생각하는 것은 (심지어 정신 활동이 그다지 풍부하지 않더라도) "마지막 사람이 이 코드에 관심이 없다면 내가 왜 신경 써야 합니까?" 또는 "그냥 엉망인 코드일 뿐인데, 누가 썼는지 알겠어요." 그것."

그러나 이것이 우리의 변명이 되어서는 안 됩니다. 이전에 다른 사람의 코드를 만질 때마다 우리는 해당 코드에 대한 책임이 있으며 코드가 효과적으로 작동하지 않으면 결과를 겪게 됩니다. 인간의 자연스러운 행동을 물리치려면 코드가 덜 더러워지는 것을 방지하기 위한 작은 조치를 취해야 합니다(깨진 창을 즉시 교체).

간단한 방법은 사용 중인 전체 패키지나 모듈에서 모든 경고를 제거하는 것입니다. 사용하지 않거나 주석이 달린 코드는 삭제하세요. 나중에 이 코드 부분이 필요한 경우 저장소에서 언제든지 이전 커밋에서 검색할 수 있습니다. 직접 해결할 수 없는 경고(예: 기본 유형 경고)가 있는 경우 @SuppressWarnings 주석으로 호출이나 메서드에 주석을 답니다. 이는 우리가 코드에 대해 신중하게 생각했음을 보장합니다. 부주의로 인한 경고가 아니라 우리가 명시적으로 알아차린 경고(예: 기본 유형)입니다.

모든 경고를 제거하거나 명시적으로 억제한 후에는 코드에 경고가 없는지 확인해야 합니다. 여기에는 두 가지 주요 효과가 있습니다.

  • 우리가 만드는 모든 코드에 대해 신중하게 생각하게 만듭니다.
  • 이제 경고로 인해 나중에 오류가 발생하는 코드 손상 변경을 줄입니다.

이것은 우리 자신뿐만 아니라 다른 사람에게도 심리적 영향을 미칩니다. 즉, 우리가 작업 중인 코드에 실제로 관심을 갖고 있다는 것입니다. 더 이상 일방통행이 아닙니다. 우리는 강제로 코드를 변경하고 커밋하며 결코 뒤돌아보지 않습니다. 대신 우리는 코드에 대한 책임을 져야 한다는 것을 인식합니다. 이는 향후 소프트웨어 개발에도 도움이 됩니다. 이는 미래의 개발자에게 이것이 깨진 창문이 있는 창고가 아니라 잘 관리된 코드 기반이라는 것을 보여줍니다.

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 linuxprobe.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제