最佳实践——它们到底来自哪里?
当我们谈论 “最佳实践” 时,我们可以区分三种情况,如下:
-
数十年来被证明行之有效的原则,这些原则也是从常识中推导出来的:在这类原则中,我们可以找到设计模式。简而言之,如果你不知道,这些都是解决经常出现的编程问题的工具。它们已经存在了几十年,为数百万开发人员所熟知。
-
我们不得不做出这样的选择:在这里,我们可以找到代码风格、命名规则等内容。从技术上讲,使用
camelCase
还是snake_case
来命名文件并不重要。但如果每个人都遵循相同的规则,大家就更容易相互理解了。 -
"技术" 最佳实践:有些 "最佳实践" 实际上是由技术限制和特性决定的。一个具体的例子就是 PHP 中的 "方法名猜测": 超文本预处理器(PHP)。假设你想猜测一个 PHP 类属性的
getters
(访问器)和setters
(更改器)。你可能会尝试以get
、set
、is
等开头的方法。如果每个人都有自己的访问器和突变器命名规则,那么可以肯定的是,总有一天,事情会毫无征兆地爆发。
设计模式原则
设计模式案例描述了解决问题的客观方案。你可以不喜欢它们,也可以不喜欢它们组织代码的方式,但不能说它们客观上不好。因为你的想法并不重要,重要的是它们行之有效。
说到已经存在了几十年的原则,我们可以强调四个著名的原则: DRY、KISS、YAGNI 和 SOLID。
DRY
DRY原则(Don’t Repeat Yourself)是编程和设计中的一个重要原则。这个原则的主要思想是避免重复的代码或设计元素。按照 DRY 原则,每个逻辑、功能或代码片段只应在一个地方定义和实现,然后在需要的地方引用或调用。
这个原则的好处包括:
-
代码简洁性:减少重复代码意味着代码库更加简洁,更容易阅读和维护。
-
易于修改:如果某个逻辑或功能需要修改,只有一处代码需要更改,而不是在代码的多个地方。
-
减少错误:重复的代码增加了错误的可能性,因为每次重复都需要正确地复制和粘贴,并可能需要进行适当的修改。减少重复减少了这种错误的机会。
为了实现 DRY 原则,开发者可以采取以下方法:
-
函数/方法提取:如果发现代码中有重复的逻辑,可以考虑将其提取为一个单独的函数或方法,然后在需要的地方调用它。
-
模块化设计:将相关的功能和代码组织成模块。每个模块都有明确的责任和功能,可以在不同的项目中重用。
-
使用参数和变量:避免使用硬编码的值,尤其是在多个地方使用相同的值。使用参数和变量可以使代码更加灵活,并减少重复。
-
继承和接口:在面向对象编程中,使用继承和接口来共享公共功能和属性,避免在每个类中重复相同的代码。
总的来说,DRY 原则鼓励开发者避免不必要的重复,提高代码的可维护性和重用性。
KISS
KISS原则(Keep It Simple, Stupid)是一种设计哲学,旨在强调简单和明确的重要性。这个原则主张在设计系统、编写代码或开发任何产品时,都应保持其简单和直观,避免不必要的复杂性。
KISS 原则的核心思想是,简单的设计更容易理解和维护。一个过于复杂的设计不仅难以理解和实现,而且容易导致错误和更高的维护成本。因此,按照 KISS 原则,设计师和开发者应该努力将系统和产品设计得尽可能简单。
为了实现 KISS 原则,可以注意以下几个方面:
-
明确需求:深入了解并明确用户需求,避免开发不必要或过于复杂的功能。
-
简洁的设计:在设计过程中,尽量去除多余的元素和功能,保持设计的简洁和直观。
-
避免过度优化:不要过早或过度优化代码或设计,这往往会引入不必要的复杂性。在明确需要优化之前,先保持其简单。
-
注重可用性:确保设计对最终用户友好且易于使用。避免为了追求某种效果而牺牲用户体验。
总的来说,KISS 原则提醒我们在开发和设计中注重简洁和明确,避免不必要的复杂性,从而降低成本、提高效率和用户体验。
YAGNI
YAGNI原则(You Aren’t Gonna Need It)是一种软件开发和项目管理的方法。这个原则主张只去实现那些当前真正需要的功能,而不是提前去预测和实现未来可能需要的功能。这个原则的主要目标是避免过度设计和过度开发,因为这可能会导致系统过于复杂,难以维护和理解。
这个原则的核心思想是在开发过程中尽可能保持简洁和精益。开发者应该专注于满足用户当前的需求,而不是去预测未来可能的需求。因为未来的需求往往是不确定的,预测未来需求并提前实现这些需求可能会导致资源的浪费,同时也增加了系统的复杂性和维护成本。
在实施 YAGNI 原则时,开发者可以采取一些策略,例如:
-
尽可能延迟决策:只有当某个功能真正需要时,才去做设计和实现决策。
-
最小可行性产品:先发布一个包含最基本功能的产品,然后根据用户反馈逐步增加功能。
-
关注当前需求:深入理解并满足用户当前的需求,而不是去预测未来的需求。
总的来说,YAGNI原则是一种提醒开发者关注当前需求,避免过度设计和开发的方法,以此来提高软件开发的效率和效果。
注意:这显然需要根据具体情况而定。我们可以以魔数为例。魔数是常数值,主要是数字,是硬编码的,没有任何含义解释。 |
结论:两周后,大家都忘了这个数字对应的是什么。这时,我们就会想到使用名字好听的代码常量。然而,这个代码常量的值很有可能永远不会改变,因为需求已经发生了变化。乍一看,声明一个常量并在所有地方使用它的做法很奇怪。代码常量的目的是为固定值添加语义,让我们可以轻松地在代码中使用该值的任何地方一次性更改该值。从某种意义上说,这与 YAGNI 背道而驰(因为这些值可能永远不会改变)。
不过,我们可以看到使用常量的价值。无论采用何种简洁代码原则,透视和反思总是必要的。
SOLID
最后,也许是最有名的一个,SOLID。
SOLID 原则是面向对象编程和面向对象设计的五个基本原则,这些原则旨在使软件更易于维护和扩展。SOLID 是五个原则的首字母缩写,分别是单一职责原则(Single Responsibility Principle)、开闭原则(Open/Closed Principle)、里氏替换原则(Liskov Substitution Principle)、接口隔离原则(Interface Segregation Principle)以及依赖反转原则(Dependency Inversion Principle)。
-
单一职责原则(SRP):一个类只负责一个功能领域中的相应职责。如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责的能力。简单地说,这意味着代码中的一个类必须只响应一项任务。显然,任务的大小是这里的关键点。我们并不是要创建一个只有一个可用方法的类。相反,我们说的是创建一个逻辑分解。一个非常具体的例子就是模型-视图-控制器(MVC)架构。要记住的重要一点是,必须避免使用包罗万象的类,将数据库操作、超文本标记语言(HTML)渲染、业务逻辑等组合在一起。分解必须符合逻辑。例如,一个类用于生成 HTML,一个类用于特定对象的数据库交互,等等。
-
开闭原则(OCP):对扩展开放,对修改封闭。类、模块、函数等等应该是可以扩展的,但是不可修改的。具体来说,在代码中,这一原则的具体体现是多态性的强力使用和接口的使用,而不是使用多个 if 和 else 语句的条件分支。事实上,如果使用条件分支,就会导致类的修改,如果出现以上的情况,就会很快变得难以管理。通过扩展一个类并重载你感兴趣的方法,你就能得到简洁的代码,而且不会出现几百行的分支。
-
里氏替换原则(LSP):子类型必须能够替换掉它们的父类型。也就是说,在使用父类型的任何地方,都可以使用子类型来替换。这个原则简单地说就是,当你使用一个接口实现时,你应该可以用另一个实现来替换它,而无需以任何方式修改该实现。在代码中,这意味着一个接口的实现必须是相似的,尤其是在方法的返回值方面(如果一个实现在调用 foo 方法时返回一个字符串,而另一个实现返回一个对象,那就复杂了)。幸运的是,在最近的 PHP 版本中,返回值的类型化限制了违反这一原则的可能性。
-
接口隔离原则(ISP):使用多个特定的接口,而不使用单一的总接口,客户端不应该被强制依赖于它们不使用的接口。具体地说,这一原则可以防止你创建一个包含几十个方法的接口,而这些方法只是为了以防万一。最好是创建几个接口(加入单一责任原则),即使是很小的接口,也要有非常明确的目标和责任。PHP 允许一个类实现任意多的接口,所以这完全没问题。正因为如此,我们所描述的实现将只知道对它有用的方法。否则,我们就会看到几十个方法的主体都是空的,或者返回一个空值。这样一说,你就会意识到,这听起来并不是什么 "干净的代码"。
-
依赖反转原则(DIP):要依赖于抽象,不要依赖于具体。实现类之间的依赖通过抽象(接口或抽象类)进行,避免类之间的直接耦合。例如,当你键入一个方法的参数时,你使用接口作为类型。这样就能充分利用多态性和 Liskov 替换。使用接口作为类型,就可以将接口的任何实现作为参数发送给函数。举个例子,如果你需要在应用程序中发送电子邮件,你很可能会创建一个 MailerInterface 接口。这样,每个邮件服务就有一个实现。通过键入带有接口的参数,该方法就能接收任何实现,并根据具体情况使用正确的邮件服务。
遵循SOLID原则可以帮助开发者创建出更加健壮、可维护和可扩展的代码,从而提高软件的整体质量。
我们很快就会意识到,这些原则是紧密联系在一起的。当你在编写代码时牢记这些原则,它们就会相互配合,从而实现很强的解耦,很强的责任分离,以及流畅的思维。记住这些原则会非常有帮助;至少,知道它们的存在是一件特别好的事情。除了 SOLID
原则之外,你还可以看到 KISS
、DRY
和 YAGNI
都是非常常识性的逻辑原则。时不时地记住它们会让我们受益匪浅,并能帮助我们在稍稍偏离轨道时设置障碍。
侦察兵原则
还可以补充一点,也是常识性的原则,那就是 "童子军原则"。我们都知道,有一些年轻人和青少年团体抱着善意的目的行事,表现出极大的利他主义。童子军到森林里露营,生火,在那里过夜。早上起床后,他们可能会把火熄灭,把自己的东西收起来,但最重要的是,他们会把那里打扫得比他们来之前更干净(至少理论上是这样)。
作为开发人员,也是如此。做一个侦察兵。在探索和浏览代码时,如果时间和上下文允许,清理技术债务往往是一个特别聪明的主意。如果你在浏览代码库中的某些地方时觉得 "这真的很糟糕",也许这就是一个让代码更易于管理、更简洁的机会。如果每个人都参与进来,项目源代码的质量就会迅速提高。
当然,这种 "童子军原则" 必须根据项目限制、时间限制和客户需求来执行。此外,这样做的风险很大,你必须知道何时该停止。当你将修改意见发送给团队审核时,你所做的修改和清理必须保持一致。你不希望每次发现一个小问题就重写半个应用程序,这样会导致另一个问题,然后又是另一个问题,如此反复。更重要的是清理与你正在做的事情相关的东西。要保持专注并固定在你的情境中,可能会非常复杂。对于 "何时停止",没有真正的答案;这在很大程度上取决于你的时间和任务。不过,没有什么可以阻止你写下那些你想回头再看的东西,但遗憾的是,这些东西与你正在做的事情无关,似乎太耗费精力和时间,或者仅仅需要与团队一起进一步思考。
与此相反,代码风格、命名约定以及类似的东西都会受到品味和习惯的影响。每个人都有自己的喜好和习惯,因此需要做出决定。正如我们前面所讨论的,如果大家都遵守同样的规则,那么在一起交流就会容易得多。
那么,在一个团队或组织中,谁来决定这些最佳做法呢?一般来说,这是在项目开始时经过长时间讨论后达成的共识。因为是的,"最佳实践" 并不是在任何地方都能适用的,你应该意识到这一点。你应该了解你所处的环境。