决定项目成败的三件事 - 企业工艺


以下三点使您成功完成任何项目的90%的方法(不考虑可能的组织问题):

  • 跟随YAGNI和KISS
    • YAGNI代表“您将不再需要”,并主张不要花时间在目前不需要的功能上
    • KISS致力于使其余功能保持简单
  • 实施域驱动设计(DDD)。尤其是:
    • 专注于核心领域
    • 维护封装
  • 做单元测试

#1:跟随YAGNI和KISS
第一个遵循YAGNI和KISS原则。
YAGNI代表“您不会需要它”,并主张不要花时间在目前不需要的功能上。
考虑一下软件开发人员每天编写的所有代码,并问自己-其中有多少真正符合客户需求?和真正的我的意思是这样的,该项目将不无代码满足其功能性和非功能性需求?我敢打赌它不到100%。在某些情况下,明显更少。
我记得自己在编程生涯的初期。对于任何任务,无论多么简单,我都试图预见未来的需求,并尽可能多地布置“扩展点”,以防万一我们需要它们。结果,该应用程序发展成为一个框架,在该框架中,当前需求是该应用程序能够提供的功能强大得多的特殊情况。我的目标是构建一个经得起时间考验并且即使面对可能的需求变更也不需要进行大量修改的应用程序。
当然,这种思路是完全落后的。扩展点永远不会出现在正确的位置;它们阻碍了未来发展。一个真正可扩展且易于更改的应用程序是您在其中延迟尽可能多的体系结构(即固定的)决策的应用程序。您不知道未来的需求。您最好的选择是根本不要做出这样的预测。
始终针对您的要求构建尽可能具体的解决方案。如此具体,以便于理解和更改(如果需要)。总是问自己这样的问题:
  • 我真的需要一个基于事件的应用程序,而基于CRUD的简单应用程序就可以了吗?
  • 我真的需要具有使用暂停恢复功能异步处理任何请求的功能吗?

我们是好奇的生物,经常以“从长远来看会有所回报”的态​​度合理化尝试新事物的愿望。它几乎永远不会。不要落入这个陷阱。
因此,再次,不要花时间在这个特定时刻不需要的功能上。您不应该开发该功能,也不应修改现有代码以考虑将来出现这种功能。两个主要原因是:

  • 机会成本  -如果您花时间在业务人员当前不需要的功能上,那么您就将时间从他们现在不需要的功能上转移了。此外,当业务人员最终需要开发功能时,他们对功能的看法很可能已经演变,您仍然需要调整已经编写的代码。这种活动是浪费的。当实际需要该功能时,从头开始实施该功能将更为有益。
  • 项目中的代码越少越好。引入代码只是为了以防万一,而不需要立即使用,从而不必要地增加了代码库的拥有成本。最好将引入新功能的时间推迟到项目的最后阶段。解决方案所需的代码越少,代码越简单越好。

KISS原则代表“保持简短和简单”。它类似于YAGNI,人们经常将两者混为一谈。但是,尽管最好将这两个原则结合在一起使用(这就是为什么我将它们都排在第一位的原因),但从技术上讲,它们并不是一回事。

  • YAGNI即将切断不必要的功能;
  • KISS致力于使其余功能尽可能简单:

很难高估简单性的重要性。简单的代码使您轻松理解它,这是大多数应用程序最重要的属性。
听起来可能有争议,但是请这样考虑。更容易理解系统或系统的正确性哪个更重要?
在短期内,正确性当然更重要。但是,只要您继续进行开发,可读性就会开始发挥越来越大的作用。没有错误但无法读取的代码的情况是不稳定的。最有可能的是,由于您无法完全理解该代码库,因此您将在以后的更改中引入错误。另一方面,使用简单易懂的源代码,您可以快速找到并修复这些错误。


#2:实施域驱动设计(DDD)
关于DDD中的战术和战略模式,有很多话要说。这些模式确实是有帮助的(只要它们不与YAGNI和KISS相矛盾),但是如果我将DDD归结为绝对必要条件,我会这样说。DDD最重要的部分是:

  • 专注于核心领域,
  • 保持封装。

您可以自行决定选择使用实体,值对象,聚合和其他典型DDD模式。不使用它们也很好。但是,以上两种实践是DDD的本质,在任何项目中都必须始终遵循。
那么,它们到底是什么意思?
专注于核心领域意味着:

  • 对代码库进行结构设计,使其在域模型中具有众所周知的,明确定义的位置。该领域模型是领域知识的有关项目是为了解决问题的集合。这是使您的应用程序与其他应用程序区分开来并为组织提供竞争优势的原因。
    为域模型分配明确的边界可以帮助您更好地可视化代码并推理出该部分代码。边界本身可以采取单独的程序集或名称空间的形式。只要所有域逻辑都放在一个单独的保护伞下,细节就不那么重要了。
  • 投入领域建模。通常,在满足新项目要求时,程序员首先要设计数据库结构,然后开始绘制UI以及与外部系统的交互,然后将所有这些放在一起。领域逻辑(又称业务逻辑)只浏览了一下,并散布在整个代码库中-介于UI,数据库和无数“服务”之间。
    这与您应该如何进行项目开发相反。域逻辑是应用程序中最重要的部分。它不仅必须在代码库中拥有自己独特的位置,还应将其放在所有其他组件之前,放在第一位。

很难高估领域模型周围显式边界的重要性。这不仅涉及为域类提供单独的目录。它还涉及将这些域类与进程外依赖关系和所有其他应用程序问题隔离开,从而使其保持纯净和集中。
保持适当的域模型边界的最佳方法是遵守六边形体系结构。使用它,您可以使用两层来表示应用程序:域模型和应用程序服务。

应用程序服务层位于域层之上,并协调该层与外部世界之间的通信。例如,如果您的应用程序是RESTful API,则对此API的所有请求都会首先到达应用程序服务层。然后,该层协调域类和进程外依赖项之间的工作。
应用程序服务层和域层的组合形成一个六边形,它本身代表您的应用程序。它可以与其他应用程序进行交互,这些应用程序以自己的六边形表示。这些其他应用程序可以是SMTP服务,第三方系统,消息总线等。一组相互作用的六边形构成六边形体系结构:

六角形/六边形架构是一组交互的应用程序。
六边形架构一词是由Alistair Cockburn提出的。其目的是强调三个重要准则:

  • 域和应用程序服务层之间的关注点分离  -由于业务逻辑是应用程序中最重要的部分,因此域层应仅对该业务逻辑负责,而无需承担其他所有责任。这些职责(例如与外部应用程序通信和从数据库检索数据)必须归因于应用程序服务。相反,应用程序服务不应包含任何业务逻辑。他们的职责是通过将传入的请求转换为对域类的操作,然后保留结果或将其返回给调用方来适应域层。您可以将域层视为应用程序领域知识的集合(操作方法)和应用服务层为一组业务用例(什么对的)。
  • 应用程序内部的通信  —六角体系结构规定了一种单向依赖关系流:从应用程序服务层到域层。域层内部的类仅应相互依赖。它们不应依赖于应用程序服务层中的类。该指南源于上一个指南。应用程序服务层和域层之间的关注点分离意味着前者了解后者,但事实并非如此。域层应与外界完全隔离。
  • 应用程序之间的通信  -外部应用程序通过应用程序服务层维护的公共接口连接到您的应用程序。没有人可以直接访问域层。六边形的每一侧代表进入或退出应用程序的连接。

同样,六边形体系结构是保持对核心领域关注的最好方法。域模型位于六边形的中心并不是巧合。

维护封装
封装是难题的另一个重要部分。仅定义具有明确边界的模型层是不够的。您还需要使该域模型内的所有操作保持内部一致。
这就是封装起作用的地方。封装是一种保护代码免遭不一致(也称为不变违规)的行为。一个不变的是,应始终保持正确的条件。
从长远来看,封装对于代码库的可维护性至关重要。原因是复杂性。代码复杂性是您在软件开发中将面临的最大挑战之一。代码库变得越复杂,使用起来就越困难,从而导致开发速度下降和错误数量增加。
没有封装,您将没有实际方法来应对不断增长的代码复杂性。当代码的API不能指导您完成该代码的内容和不允许执行的操作时,您必须记住很多信息,以确保不会引起与新代码更改的不一致。这给编程过程带来了额外的精神负担。尽可能减轻您的负担。您不能一直相信自己会做正确的事-消除了做错事的可能性。 最好的方法是保持适当的封装,以使您的代码库甚至不提供错误执行任何操作的选项。
封装可以通过以下方式实现:

  • 减少用于数据修改的API表面积,
  • 仔细检查所有此类API。

作为程序员,您应该两者兼而有之。您应该消除尽可能多的数据变异操作;并且您还应谨慎维护其余此类操作,以确保内部一致性。
函数式编程将这两个指导方针发挥到了极致,并消除了所有可变的操作。使用不可变的类,您不必担心内部状态的损坏,因为不可能破坏最初无法更改的内容。因此,无需在函数式编程中进行封装。创建类的实例时,只需验证一次类的状态。之后,您可以随意传递该实例。当您的所有数据都是不可变的时,与缺乏封装相关的整个问题就会消失。

#3:进行单元测试
单元测试是每个项目为了长期成功而必须实践的第三件事。
但是,仅进行单元测试是不够的。只有高价值的测试才值得保留在您的测试套件中。关于如何构建一个非常有价值的测试套件(我在我最近的有关单元测试的书中做了),有很多要说的,我将尝试在后续文章中介绍最重要的部分。
但是,最有趣的一点是,如果不关注核心领域和维护封装,几乎不可能编写出有价值的测试。
编写更好的单元测试将迫使您封装域模型并构建设计良好的API。封装又迫使您改善单元测试。