清洁代码:职责 — Janos Pasztor

19-01-09 banq
                   

我听说你想成为一个更好的程序员。您希望使用可重用的部分,并希望更轻松地维护旧代码。您可能还希望在团队中更好地工作并确保减少错误。

对更好代码的渴望通常会让人们发现“清洁代码”这个术语。这很可能是由罗伯特C.“鲍勃叔叔”马丁创造的 ,他写了一本同名的书。你可能想给它一个阅读,但是,我发现它非常冗长。本书介绍了一些基本原则,这些原则可以帮助您编写模块化代码,以便以后可以重用这些模块。在本系列中,我们将介绍他的原则和想法,以及清洁代码运动的其他一些作者的原则和想法。

您可能已经注意到,我使用了名称“module”而不是“class”或“object”。那是因为干净的代码不是特定于面向对象的编程。您可以将干净的代码原则与您喜欢的任何编程范例一起使用,例如,经典的过程编程。

那么让我们开始讨论我们议程上的第一个主题:何时是拆分模块的好时机?

责任

编写代码时,必须以某种方式对其进行分段。组织单位可以是类或模块,具体取决于您的编程范例。一般的想法是,一段代码应该只处理一个责任。换句话说,做一件事,做得好。

但是责任是什么?让我们上一节课,例如,与学校的学生打交道。该课程将保留一个学生的数据。像这样:

class Student {
  private string name;
  
  public void setName(string name) {
    this.name = name;
  }
  
  public string getName() {
    return this.name;
  }
}

您需要将数据保存在某个位置,例如,在数据库中。问题出现了:实现save()存储此学生记录的方法是一个好主意吗?毕竟,这将非常方便:

Student joe = new Student();
joe.setName('Joe');
joe.save();

想象一下以下情况。您在考虑MySQL的情况下实现此类,这是Web世界中相当标准的数据库引擎。一个忠实的日子,你的老板来找你并告诉你系统管理员一直抱怨,服务器超载。在短暂的搜索之后,您发现您的students桌子非常庞大而且速度很慢,因此您现在决定为您的学生数据实施缓存。数据从MySQL读取并写入例如Memcache。

所以现在你的Student类需要知道MySQL和Memcache。到目前为止Student,只能让您轻松访问学生数据的简单类已经发展到相当大的规模,现在出现了维护问题。有很多代码甚至无法测试。但是,嘿,这就是生活吧?

接下来的一周,似曾相识,你的老板又回到了你的办公桌前。系统管理员再次抱怨。(他们不能只购买更多的硬件吗?来吧。)现在这是你的courses桌子造成的问题。您决定使用相同的路径并将Memcache的代码复制到您的Courses班级。

是的,是的,我可以听到你尖叫,你永远不会那样做。您总是决定重构代码以避免重复。但相信我,其他人不会。除非您单独工作,否则您将不得不与那些对重复代码具有更高容忍度的人打交道。

那么我们怎样才能避免这种情况呢?即使不是您编辑代码,我们怎样才能确保不会发生这种情况?答案在于责任这个词。用我们的save()方法我们犯了一个错误。我们在一个班级中承担了不止一项责任。该Student班负责保存学生记录并将其保存到数据库。

用伟大的鲍勃叔叔的话来说,如果一个类只有一个改变的原因,那么它就有一个责任。

前面的例子很清楚:有两个职责,存储/检索数据和数据结构本身。这些应该解耦,以便我们可以独立地改变它们。

那么我们如何解决这个问题呢?记住,我们说过我们想让一个班级只有一个责任。所以让我们把它分成两部分。让我们按原样保留原始Student类,并创建第二个类来存储和检索Student对象。

class MySQLStudentStorage {
  public Student getById(int id) {
    //...
  }
  
  public void store(Student Student) {
    //...
  }
}

当然,当你几乎没有代码时,这很容易。但是如果你已经拥有大量依赖save()函数的代码,你会怎么做?那么,你处境艰难,没有完美的解决方案。

例如,您可以通过Student类中的调用代理:

class Student {
  //...
  
  public void save() {
    storage = new MySQLStudentStorage();
    storage.store(this);
  }
}

使用此代理解决方案将极大地帮助您,因为您可以一次重写一个模块的代码,并且您不需要对生产环境进行大的更改。当您需要拆分类时,几乎可以在所有情况下使用代理策略。

重构

让我们看一个不同的例子。想象一下处理财务数据的系统。你知道,这是无聊的东西。您很可能拥有某种类型的数据库,并且您必须向使用它的人员提供一些报告。这些报告将是Excel表格或某些描述的CSV文件。

其中一份报告可能是每月收入/支出表。最初,您为财务人员构建了此表,以便他们可以根据您提供的数据运行他们的奇特报告软件。有一天,令你惊讶的是,首席执行官走进你的房间,并要求你改变报告的一栏。她希望你很好地格式化数字,这样她就更容易阅读。当然,您希望让您的首席执行官感到高兴,因此您需要改变这一栏。

一切都很顺利,直到本月末,财务人员再次尝试使用该报告并将其导入到他们的软件中。一切都崩溃了。他们希望修复报告。我不知道你是否遇到了财务人员,但如果他们说他们希望他们的报告得到修复,那就意味着他们现在想要修复它。毕竟,这是本月底,他们需要提供自己的报告,如果因为你迟到,没有人会诅咒。

你是做什么?显然,你还原了之前的改变。然后你给首席执行官写了一封抱歉的信,下次你想要加薪时,你的记分卡肯定不会好看。或者您可以花时间重构代码以提供两个单独的报告而不是一个报告,并将其作为两个功能出售给CEO。毋庸置疑,你必须花一些时间在上面,但拥有专门的CEO报告比在你的记录上留下黑色标记要好得多。此外,下次首席执行官要求您更改某些内容时,您可以自由地这样做,因为您确保一个模块只负责一件事,只有一个改变的理由。

结论

如您所见,本文中概述的单一责任原则非常符合商业业务利益。让您编写遵循此想法的代码符合公司的长远利益。将变化压缩到不属于的类中将导致更大和更大的类,并导致巨大的维护难题。复杂的代码也意味着您需要进行的任何更改都需要数月而不是数天,因为您必须解决代码中有机发展的混乱。