当前位置: 首页 > 后端技术 > PHP

[译]软件复杂性:命名的艺术

时间:2023-03-29 21:16:17 PHP

软件复杂性:命名的艺术计算机科学中只有两件难事:缓存失效和命名约定。——PhilKarlton前言编写高质量的代码本身就是一件非常困难的事情,为什么这么说呢?因为好的编码风格是为了更好的理解和阅读。通常我们只关注前者而忽略了后者的重要性。我们的代码写了一次,但在审查期间阅读了很多次。良好的编码习惯可以提高我们的阅读质量,这比自己编写要容易得多。我们可以从宏观的角度看问题,从远处看大局,不失细节。首先,我们需要把某个问题理解清楚,分析清楚,然后以独特、高效、简洁的方式让更多人理解它。对我来说,软件工程应该明确地归类为社会科学领域。我们为谁编码,不是为人类吗?(感觉原文作者有点太矫揉造作了)把我们的想法和编程思路传达给他人,这就是我们在编码时所做的事情。命名结构为了说明我们的第一个概念,我们先做一个游戏,这个游戏叫做“我们住在哪个房间?”,下面给大家提供一张图片,请告诉我这是一个什么样的房间。问题1/3从上图不难看出,这里一定是客厅。基于一个item,我们可以联想到一个房间的名字,这个很简单,请看下图。Question2/3根据这张图片,我们可以肯定地说,这是厕所。通过上面两张图不难发现,房间的名字只是一个label属性。有了这个标签,我们甚至不需要看里面是什么。这样,我们就可以建立第一个推论:推论1:容器(函数)的名称应该包含里面的所有元素。这种推断可以理解为鸭子类型。如果有床?然后是卧室。我们也可以反过来分析。问题:根据容器名称,我们可以推断出它的组件。如果我们以卧室为例,那么这个房间很有可能有一张床。这样,我们就可以建立第二个推论:推论2:根据容器的名称推断容器(函数)的内部组件现在我们有两个推论,那么我们试着看下图。问题3/3那么,床和厕所在同一个房间吗?根据我们的推断,上图让我们很难立即做出判断。如果我们还是用上面的两个推论来定义它,那我就称它为:怪物的房间。问题不在于同一房间中的物品数量,而在于完全不相关的物品被认为具有相同的标签属性。在国内,我们通常把相关的、意图的、功能的东西放在一起,以免混淆大众,所以现在我们有第三个推论:推论3:容器(功能)的清晰度与其内部组件密切相关这可能很难理解,所以我们用下图来说明:如果容器内部元素的属性是高度相关的,那么我们就更容易找到一个名字来描述它。反之,元素之间的不相关性越强,就越难描述。属性维度可能与其功能、用途、策略、类型等相关。关于命名标准,需要与元素本身的属性相关联才具有实际意义。按照我的思路,我们很快就会看到这一点。软件工程也是如此。例如,我们熟悉的组件、类、功能方法、服务和应用程序。RobertDelaney曾说过:“我们的理解能力很大程度上与我们的认知有关”,那么在这种技术背景下,我们的代码能否让读者以最简单的方式感知业务呢?需要和相关要求?示例1:HTTP域和汽车HTTP本身就是一个域环境,里面包含了我们的网络请求和响应状态。如果我们将Car组件放入其中,我们就不能再将其称为HTTP,在这种情况下它会变得混乱。publicinterfaceWhatIsAGoodNameForThis{/*汽车的方法*/publicvoidgas();公共无效刹车();/*HTTP客户端的方法*/publicResponsemakeGetRequest(Stringparam);}例子2:词的耦合有一个常见的命名模式是在名称后缀上加上一个像Builder或er这样的结尾词,例如:SomethingBuilder、UserBuilder、AccountCreator、UserHelper、JobPerformer等。例如上图中的名称,我们可以推断出三件事。首先,在类名中使用动词Build暗示它是功能性的。其次,它由两部分组成,一个是User用户,一个是Builder构造函数,也就是说他们之间在封装和维度分类上可能存在歧义。第三,Builder构造函数可以访问到类内部User用户的相关逻辑和数据,因为他们在同一个纬度空间。这和工厂模式很相似,都有自己的应用场景。当它在我们的项目中大量使用时,会是一个非常麻烦的问题。另外需要提醒大家的是,在工厂模式下,并不是一定要有类的。一个createUser方法就可以很好的实现工厂模式的功能。示例3:基类让我们先来看一些现实生活中的例子。首先是i18nRubygem(它的类名和方法名非常简洁)。classBasedefconfigdeftranslatedeflocale_available?(locale)deftransliterateend在这里,Base这个名字本身并没有传达太多意义,内部结构包括配置、翻译、区域设置和音译。他们可以看似毫无关联地走到一起。实例四:命名和构建一个合理的命名可以指导我们构建一个更紧凑的组件容器。如下例所示。classPostAlerterdefnotify_post_usersdefnotify_group_summarydefnotify_non_pm_usersdefcreate_notificationdefunread_postsdefunread_countdefgroup_statsendPostAlerter从名字本身就可以看出,意思是它内部会做一些类似于提醒通知的功能。但是unread_posts,unread_count,group_status不在这个函数的主要范围内,从这个角度来看,这个类的名字也不是很理想。我们可以把这三个方法移到一个叫PostStatistics的类中,这样解耦后,事件函数会变得更清晰,更可预测。类PostAlerterdefnotify_post_usersdefnotify_group_summarydefnotify_non_pm_usersdefcreate_notificationendclassPostsStatisticsdefunread_postsdefunread_countdefgroup_statsend这里只是一个例子(因为有太多):很多不合理的名字,而D3的arc中有很多很好的命名定义,例如:exportdefaultfunction(){/*...*/arc.centroid=function(){/*...*/}arc.innerRadius=function(){/*...*/}圆弧。outerRadius=function(){/*...*/}圆弧。cornerRadius=function(){/*...*/}arc.padRadius=function(){/*...*/}arc.startAngle=function(){/*...*/}arc.endAngle=function(){/*...*/}arc.padAngle=function(){/*...*/}returnarc;}在上面的示例中,每个方法都非常有意义:它们都以arc开头.而他的命名风格,就像下图画的一样,简单又悦耳。方法一:拆解应用场景:当你找不到一个合适的类或方法的名字,但你知道如何拆解它们,并期望为它们的组合找到一个好的名字。主要有两个步骤:区分它们的特点和概念,将它们分开。在床和厕所的具体耦合场景中,为了拆解它们的区别,我们将床向左移动,将床向左移动。厕所向右移动。这样我们就把两个不同的东西分开了。当您无法为某物找到一个好名字时,也许是因为您正在处理不止一件事。但是现在我们知道,要命名多个事物是非常困难的。当我们遇到这样的问题时,不妨确认一下构成这个东西的组件和动作。示例:我们有一个未命名的类,其中包含请求、响应、标头、URL、正文、缓存、超时,将所有这些从类中拉出,我们剩下一些组件:Request、Respone、Headers、URL、ReponseBody、Cache,Timeout等。如果我们知道这些类的名字,那么我们就可以确定这个类是用来处理web请求的,HTTPClient是web请求组件的一个好名字。当我们在编码中遇到困难时,不要考虑整体,先考虑局部细节。方法二:发现新概念应用场景:当一个类不简单或内容无关紧要时。发现新概念需要大量业务领域的知识。当软件的命名与业务一致时,就建立了一种通用语言,可以让来自不同专业领域的人使用同一种语言。方法三:分组标准应用场景:当有一个好的命名,但他们之间不符合时。组件元素以前可以根据各种标准进行分组,例如组件元素的物理属性、经济、情感、社交和软件中最常用的功能。在软件工程中,我们倾向于按功能对组件元素进行分组。如果您列出您的项目文件,您可能会看到目录名称,如controller/、models/、adapters/、templates/等。然后,有时,这些名称组合在一起并且必须适合。这也是重新评估模块。重新定义时,规划命名。每个应用程序都有自己不同的上下文,每个模块、每个类、每个方法也是如此。User这个词的含义可以是操作系统用户,也可以是数据表,也可以是第三方服务凭证。它在不同的上下文中有不同的含义。废话与新词多年来,命名约定已经演变为更有意义,更多人填补了陈旧的空白。Helper,helpers是支持应用程序实现的主要方式。应用程序实现和定义的标准是什么?应用程序中的所有内容都应支持并实现其主要目的。实际上,它们以一种不自然的方式组合在一起,以便为其他常用操作提供可重用性。通常,助手需要依赖另一个组件元素的内部数据。这种命名一般是在找不到合适的名字时作为折衷。Base,很久以前,在C#中,需要继承的类都是以Base命名的。例如:Car和Bicycle的父类都是Base而不是Vehicle。尽管微软建议避免这种命名约定,但他仍然影响了Ruby语言,最显着的是ActiveRecord类的继承。到目前为止,我们仍然将Base视为开发人员找不到合适名称的替代方案。改动调整后的Base包括Common和Utils。比如JSONRubygem的Common类有parse、generate、load、jj等方法,但是Common在这里真的有它的意义吗?任务,一种异步调用函数的方法已经出现在JavaScript社区。该方法源自task.js。尽管这个开源库已经很少使用了,但这个术语已经流传了下来。如果团队中的每个人都能清楚地理解其中的含义,那将是令人欣慰的。但是,如果一个新人加入团队并且他遇到了一些被扔进垃圾桶的60年代古怪的术语怎么办?在我之前的项目工作中,遇到过这样的类命名,猜猜看,Atlanta,对,Atlanta,fuckAtlanta。没有人知道或可以告诉我为什么这样命名以及它的含义。参考资料Cwalina,Krzysztof.2009,框架设计指南:可重用.NET库的约定、习语和模式,第二版。波士顿:PearsonEducation,Inc.206。埃里克·埃文斯。2003.领域驱动设计:解决软件核心的复杂性。波士顿:Addison-WesleyProfessional。