在参与了多起项目之后,我注意到每个项目都存在一些共通的问题,不论项目的领域、架构、代码约定等等。虽然这些问题并不具有挑战性,只是一些乏味的例行程序,但目的是为了确保你不会犯任何明显的错误。我不想日复一日重复这些例行程序,我倒是很希望找到解决方案:一些开发方法、代码约定或其他任何可以帮助我避免这些问题发生的方式,这样在设计项目的时候,我就可以专心做自己感兴趣的工作了。这就是本文的目标:描述这些问题,并向你展示我解决这些问题的工具和方法。
我们面临的问题
在开发软件的过程中,我们会遇到很多困难,例如需求不明确、沟通不畅、开发过程不顺利等等。此外,我们还面临一些技术难题,比如遗留代码拖后腿、棘手的规模扩展、遇到一些以往的错误决定。所有这些问题都可以得到解决或减少,但是有一个我们无能为力的基本问题:系统的复杂性。无论你理解与否,你正在开发的系统总是很复杂。即使你只是在做一个烂大街的CRUD应用程序,也总是会遇到一些极端情况,一些棘手的事情,而且还有人不时地问:“如果我在这种情况下这样做会怎么样?”你可能会说:“嗯,这是一个好问题。容我想想……”你都需要仔细考虑那些棘手的案例,含糊不清的逻辑、验证和访问管理等等。我们常常遇到某个想法过于宏大,无法一个人独自策划,而这种情况往往会衍生出误解等问题。但是,假设这个领域专家和业务分析师团队清晰地沟通并产生了一致的需求。现在我们必须实现这些需求,通过代码表达这个复杂的想法。而代码是另一个系统,比我们原始的想法更复杂。怎么会这样?这就是现实:技术上的局限性迫使你在实现业务逻辑的基础之上,还要处理处理高负载、数据一致性和可用性。如你所见,这项任务的难度非常大,现在我们需要适当的工具来处理。编程语言只是一种工具,就像其他工具一样,你不仅需要考虑编程语言本身的质量,而且还要选择适合工作的工具。工欲善其事必先利其器!
技术介绍
如今,面向对象的编程语言很流行。当有人介绍OOP时,通常他们会使用这样一个例子:假设有一辆来自现实世界的汽车,汽车有品牌、重量、颜色、最大速度、当前速度等各项属性。为了在我们的程序中反映这个对象,我们需要建立一个类收集这些属性。属性可以是永久的或可变的,所有属性一起形成该对象的当前状态和变化的一些边界。然而,仅组合这些属性还不够,我们必须检查当前状态是否有意义,例如当前速度有没有超过最大速度。为了向这个类添加一些逻辑,我们需要将属性标记为私有以防止其他人创建非法状态。可见,对象的核心就是它们的内部状态和生命周期。因此,在这种情况下,OOP的三个支柱完全合理:使用继承来重用某些状态操作;通过封装保护状态;以相同方式处理类似对象的多态性。默认可修改也很合理,因为在这种情况下,不可变对象无法拥有生命周期,而且始终处于同一个状态,这并非是常态。然而,如今常见的Web应用程序并不处理对象。我们代码中的所有东西都有永恒的生命或根本没有生命。两种最常见的“对象”类型是某种服务,比如UserService、EmployeeRepository、某些模型/实体/ DTO或任何服务。服务的内部没有逻辑状态,它们每次产生和消亡过程完全相同,我们只需建立新的数据库连接并重新创建依赖关系图。实体和模型没有任何附加行为,它们只是数据捆绑,它们的可修改性无关紧要。因此,在开发这种应用程序时,OOP的关键特性并没有用武之地。常见的Web应用程序中需要处理的是数据流:验证、转换、评估等。适合这种工作的编程语言类型为函数式编程。事实证明,目前流行语言中的所有现代功能都来自:async / await、lambdas和delegates、响应式编程、可识别联合(swift或rust中的枚举,不要与java或.net中的枚举混淆)、元组——所有这些都来自函数式编程。然而,这些只是其中一部分,除此之外还有很多很多。 在深入讨论之前,还需要交代一点。切换到新语言,尤其是新编程范式,是对开发人员的投资,也是对业务的投资。即便投资错误也不会带来任何麻烦,但合理的投资可以让你受益无穷。
工具介绍
很多人喜欢静态类型的编程语言。原因很简单:编译器负责繁琐的检查,例如将正确的参数传递给函数,正确地构造实体等等。这些检查都是自带的。至于编译器无法检查的东西,我们可以选择听天由命,或编写测试。编写测试意味着成本,而且每项测试都不是一次性的,你还需要付出维护的成本。此外,人们都比较粗心大意,所以每隔一段时间我们就会遇到假阳性和假阴性的测试结果。你编写的测试越多,测试的平均质量就越高。还有另一个问题:为了某些测试,你必须记住测试哪些功能,系统越大越容易漏掉某些功能。然而,编译器只能发挥与语言的类型系统一样的功能。如果它不接受静态方式的表达,则必须在运行时执行此操作。这种情况下你也需要测试。这不仅仅与类型系统有关,语法和语法糖特征也非常重要,因为我们肯定希望编写尽可能少的代码,所以如果某些方法需要编写10倍的代码,那么就没人用。这就是为什么你需要选择具有适合的特征和技巧的语言。否则的话,你不仅无法利用语言的功能来对抗原有的困难(比如系统的复杂性和不断变化的要求),你还需要与语言本身作斗争。因为你需要为开发人员花费的时间支付费用,所以这一切归根到底都是白花花的银子。开发人员需要解决的问题越多,他们需要的时间就越多,你需要的开发人员就越多。最后,让我们来看看实际的代码。我是一名.NET开发人员,因此以下代码示例将用C#和F#编写,但即使用其他流行的OOP和函数式编程语言编写,情况也差不多。