当前位置: 首页 > news >正文

建水网站开发广东省建设工程合同备案网站

建水网站开发,广东省建设工程合同备案网站,大连学网站制作,深圳做网页的网站**简介#xff1a;** 对于大型的软件系统如互联网分布式应用或企业级软件#xff0c;为何我们常常会陷入复杂度陷阱#xff1f;如何识别复杂度增长的因素#xff1f;在代码开发以及演进的过程中需要遵循哪些原则#xff1f;本文将分享阿里研究员谷朴关于软件复杂度的思考** 对于大型的软件系统如互联网分布式应用或企业级软件为何我们常常会陷入复杂度陷阱如何识别复杂度增长的因素在代码开发以及演进的过程中需要遵循哪些原则本文将分享阿里研究员谷朴关于软件复杂度的思考什么是复杂度、复杂度是如何产生的以及解决的思路。较长同学们可收藏后再看。 ![1.png](https://ucc.alicdn.com/pic/developer-ecology/aa880b82b14540b588cd595bcc03ad81.png 1.png) 作者 | 张瓅玶(谷朴)  阿里巴巴研究员 **导读**对于大型的软件系统如互联网分布式应用或企业级软件为何我们常常会陷入复杂度陷阱如何识别复杂度增长的因素在代码开发以及演进的过程中需要遵循哪些原则本文将分享阿里研究员谷朴关于软件复杂度的思考什么是复杂度、复杂度是如何产生的以及解决的思路。较长同学们可收藏后再看。 软件设计和实现的本质是工程师相互通过“写作”来交流一些包含丰富细节的抽象概念并且不断迭代过程。 另外如果你的代码生存期一般不超过 6 个月本文用处不大。 软件架构的核心挑战是快速增长的复杂性 越是大型系统越需要简单性。 大型系统的本质问题是复杂性问题。互联网软件是典型的大型系统如下图所示数百个甚至更多的微服务相互调用/依赖组成一个组件数量大、行为复杂、时刻在变动发布、配置变更当中的动态的、复杂的系统。而且软件工程师们常常自嘲“when things work, nobody knows why”。 ![2.png](https://ucc.alicdn.com/pic/developer-ecology/d80155588845430cacbec78f2426279c.png 2.png)   图源[https://divante.com/blog/10-companies-that-implemented-the-microservice-architecture-and-paved-the-way-for-others/](https://divante.com/blog/10-companies-that-implemented-the-microservice-architecture-and-paved-the-way-for-others/) 如果我们只是写一段独立代码不和其他系统交互往往设计上要求不会很高代码是否易于使用、易于理解、易于测试和维护根本不是问题。而一旦遇到大型的软件系统如互联网分布式应用或者企业级软件我们常常陷入复杂度陷阱下图 the life of a software engineer 是我很喜欢的一个软件 cartoon非常形象地展示了复杂度陷阱。 ![3.png](https://ucc.alicdn.com/pic/developer-ecology/5da680bdbd9c4e01892c342dac1c706b.png 3.png)   图源[http://themetapicture.com/the-life-of-a-software-engineer/](http://themetapicture.com/the-life-of-a-software-engineer/)  作为一个有追求的软件工程师大家肯定都思考过我手上的项目如何避免这种似乎难以避免的复杂度困境 然而对于这个问题给出答案却出乎意料的困难很多的文章都给出了软件架构的设计建议然后正如软件领域的经典论著《No silver bullet》所说这个问题没有神奇的解决方案。并不是说那么多的架构文章都没用其实这么方法多半都有用只不过人们很难真正去 follow 这些建议并贯彻下去。为什么我们还是需要彻底理解这些架构背后的思考和逻辑。所以我觉得有必要从头开始整理这个逻辑什么是复杂度复杂度是如何产生的以及解决的思路。 软件的复杂度为什么会快速增长 要理解软件复杂度会快速增长的本质原因需要理解软件是怎么来的。我们首先要回答一个问题一个大型的软件是建造出来的还是生长出来的BUILT vs GROWNthat is the problem. 1\. 软件是长出来的不是建造出来的 ------------------- 软件不是建造出来的甚至不是设计出来的。软件是长出来的。 这个说法初看上去和我们平时的认识似乎不同我们常常谈软件架构架构这个词似乎蕴含了一种建造和设计的意味。然而对于软件系统来说我们必须认识到架构师设计的不是软件的架构而是软件的基因而这些基因如何影响软件未来的形态则是难以预测无法完全控制。 为什么这么说所谓建造和“生长”差异在哪里 其实我们看今天一个复杂的软件系统确实很像一个复杂的建筑物。但是把软件比作一栋摩天大楼却不是一个好的比喻。原因在于一个摩天大楼无论多么复杂都是事先可以根据设计出完整详尽的图纸按图准确施工保证质量就能建造出来的。然而现实中的大型软件系统却不是这么建造出来的。 ![4.png](https://ucc.alicdn.com/pic/developer-ecology/2244b638bc2e407a8615f8875534ed9b.png 4.png) 例如淘宝由一个单体 PHP 应用经过 4、5 代架构不断演进才到今天服务十亿人规模的电商交易平台。支付宝、Google 搜索、Netflix 微服务都是类似的历程。 是不是一定要经过几代演进才能构建出来大型软件就不能一次到位吗如果一个团队离开淘宝要拉开架势根据淘宝交易的架构重新复制一套在现实中是不可能实现的没有哪个创业团队能有那么多资源同时投入这么多组件的开发也不可能有一开始就朝着超级复杂架构开发而能够成功的实现。 ![5.png](https://ucc.alicdn.com/pic/developer-ecology/84c874d57c5b479382896dac45f1f024.png 5.png) 也就是说软件的动态“生长”更像是上图所画的那样是从一个简单的“结构”生长到复杂的“结构”的过程。伴随着项目本身的发展、研发团队的壮大系统是个逐渐生长的过程。 2\. 大型软件的核心挑战是软件“生长”过程中的理解和维护成本 ------------------------------- 复杂软件系统最核心的特征是有成百上千的工程师开发和维护的系统软件的本质是工程师之间用编程语言来沟通抽象和复杂的概念注意软件的本质不是人和机器沟通。 如果认同这个定义设想一下复杂软件是如何产生的无论最终多么复杂的软件都要从第一行开始开发。都要从几个核心开始开发这时架构只能是一个简单的、少量程序员可以维护的系统组成架构。随着项目的成功再去逐渐细化功能增加可扩展性分布式微服务化增加功能业务需求也在这个过程中不断产生系统满足这些业务需求带来业务的增长。业务增长对于软件系统迭代带来了更多的需求架构随着适应而演进投入开发的人员随着业务的成功增加这样不断迭代才会演进出几十几百甚至几千人同时维护的复杂系统来。 大型软件设计核心要素是控制复杂度。这一点非常有挑战根本原因在于软件不是机械活动的组合不能在事先通过精心的“架构设计”规避复杂度失控的风险相同的架构图/蓝图可以长出完完全全不同的软件来。大型软件设计和实现的本质是大量的工程师相互通过“写作”来交流一些包含丰富细节的抽象概念并且相互不断迭代的过程。稍有差错系统复杂度就会失控。 所以说了这么多是要停留在形而上吗并不是。我们的结论是软件架构师最重要的工作不是设计软件的结构而是通过 API团队设计准则和对细节的关注控制软件复杂度的增长。 *   架构师的职责不是试图画出复杂软件的大图。大图好画靠谱的系统难做。复杂的系统是从一个个简单应用 一点点长出来的 *   当我们发现自己的系统问题多多别怪“当初”设计的人坑不是一天挖出来的。每一个设计决定都在贡献复杂度。 理解软件复杂度的维度 1\. 软件复杂度的两个表现维度认知负荷与协同成本 -------------------------- 我们分析理解了软件复杂度快速增长的原因下面我们自然希望能解决复杂度快速增长这一看似永恒的难题。但是在此之前我们还是需要先分析清楚一件事情复杂度本身是什么又如何衡量 代码复杂度是用行数来衡量么是用类的个数/文件的个数么深入思考就会意识到这些表面上的指标并非软件复杂度的核心度量。正如前面所分析的软件复杂度从根本上说可以说是一个主观指标先别跳耐心读下去说其主观是因为软件复杂度只有在程序员需要更新、维护、排查问题的时候才有意义。一个不需要演进和维护的系统其架构、代码如何关系也就不大了虽然现实中这种情况很少。 ![6.png](https://ucc.alicdn.com/pic/developer-ecology/c236d2ebd0254010a5a0f956fb4e8fe8.png 6.png) 既然 “软件设计和实现的本质是工程师相互通过写作来交流一些包含丰富细节的抽象概念并且不断迭代过程” 第三次强调了那么复杂度指的是软件中那些让人理解和修改维护的困难程度。相应的简单性就是让理解和维护代码更容易的要素。 “The goal of software architecture is to minimize the manpower required to build and maintain the required system.” Robert Martin, Clean Architecture. 因此我们将软件的复杂度分解为两个维度都和人理解与维护软件的成本相关 *   第一认知负荷 cognitive load 理解软件的接口、设计或者实现所需要的心智负担 *   第二协同成本 Collaboration cost团队维护软件时需要在协同上额外付出的成本。 我们看到这两个维度有所区别但是又相互关联。协同成本高让软件系统演进速度变慢效率变差工作其中的工程师压力增大而长期难以取得进展工程师倾向于离开项目最终造成质量进一步下滑的恶性循环。而认知负荷高的软件模块让程序员难以理解从而产生两个后果 *   维护过程中易于出错bug 率故障率高 *   更大机率 团队人员变化时被抛弃新成员选择另起炉灶原有投入被浪费甚至更高糟糕的是代码被抛弃但是又无法下线成为定时炸弹。 2\. 影响到认知负荷的因素 -------------- 认知负荷又可以分解为 *   定义新的概念带来认知负荷而这种认知负荷与概念和物理世界的关联程度相关 *   逻辑符合思维习惯程度正反逻辑差异逻辑嵌套和独立原子化组合。继承和组装差异。  ### 1不恰当的逻辑带来的认知成本 看以下案例 A. Code with too much nesting response server.Call(request) if response.GetStatus() RPC.OK:   if response.GetAuthorizedUser():     if response.GetEnc() utf-8:       if response.GetRows():         vals [ParseRow(r) for r in                 response.GetRows()]         avg sum(vals) / len(vals)         return avg, vals       else:         raise EmptyError()     else:       raise AuthError(unauthorized)   else:     raise ValueError(wrong encoding) else:   raise RpcError(response.GetStatus()) B.  Code with less nesting response server.Call(request) if response.GetStatus() ! RPC.OK:   raise RpcError(response.GetStatus()) if not response.GetAuthorizedUser():   raise ValueError(wrong encoding) if response.GetEnc() ! utf-8:   raise AuthError(unauthorized) if not response.GetRows():   raise EmptyError() vals [ParseRow(r) for r in         response.GetRows()] avg sum(vals) / len(vals) return avg, vals 比较 A 和 B逻辑是完全等价的但是B的逻辑明显更容易理解自然也更容易在 B 的代码基础上增加功能且新增的功能很可能也会维持这样一个比较好的状态。 而我们看到 A 的代码很难理解其逻辑在维护的过程中会有更大的概率引入 bug代码的质量也会持续恶化。 ### 2模型失配和现实世界不完全符合的模型带来高认知负荷 软件的模型设计需要符合现实物理世界的认知否则会带来非常高的认知成本。我遇到过这样一个资源管理系统的设计设计者从数学角度有一个非常优雅的模型将资源账号用合约来表达下图左侧账户的 balance 可以由过往合约的累计获得确保数据一致性。但是这样的设计完全不符合用户的认知对于用户来说感受到的应该是账号和交易的概念而不是带着复杂参数的合约。可以想象这样的设计其维护成本非常之高。 ![7.png](https://ucc.alicdn.com/pic/developer-ecology/235b2e0f887c4d71b829bed59269923f.png 7.png) ### 3接口设计不当 以下是一个典型的接口设计不当带来的理解成本。 class BufferBadDesign { explicit Buffer(int size);// Create a buffer with given sized slots   void AddSlots(int num);// Expand the slots by num   // Add a value to the end of stack, and the caller need to   // ensure that there is at least one empty slot in the stack before   // calling insert   void Insert(int value); int getNumberOfEmptySlots(); // return the number of empty slots } 希望我们的团队不会设计出这样的模块。这个问题可以明显看到一个接口设计的不合理带来的维护成本提升一个 Buffer 的设计暴露了内部内存管理的细节slot 维护从而导致在调用最常用接口 “insert” 时存在陷阱如果不在 insert 前检查空余 slot这个接口就会有异常行为。 但是从设计角度看维护底层的 Slot 的逻辑也外部可见的 buffer 的行为其实并没有关联而只是一个底层的实现细节。因此更好的设计应该可以简化接口。把 Slot 数量的维护改为内部的实现逻辑细节不对外暴露。这样也完全消除了因为使用不当带来问题的场景。同时也让接口更易于理解降低了认知成本。 class Buffer {     explicit Buffer(int size); // Create a buffer with given sized slots     // Add a value to the end of buffer. New slots are added      // if necessary.      void Insert(int value); } 事实上当我们发现一个模块在使用时具备如下特点时一般就是难以理解、容易出错的信号 *   一个模块需要调用者使用初始化接口才能正常行为对于调用者来说需要调用初始化接口看似不是大的问题但是这样的模块带来了多种后患尤其是当存在多个参数需要设置相互关联关系复杂时。配置问题应该单独解决比如通过工厂模式或者通过单独的配置系统来管理 *   一个模块需要调用者使用后做清理/ finalizer 才能正常退出 *   一个模块有多种方式让调用者实现完全相同的功能软件在维护过程中出现这种状况可能是因为初始设计不当后来修改设计 带来的冗余也可能是设计原版的缺陷无论如何这种模块带着强烈的“坏味道”。 完全避免这些问题很难但是我们需要在设计中尽最大努力。有时通过文档的解释来弥补这些问题是必要的但是好的工程师/架构师应该清醒的意识到这些都是“坏味道”。 ### 4一个简单的修改需要在多处更新 简单修改涉及多处更改也是常见的软件维护复杂度因素而且主要影响的是我们的认知负荷维护修改代码时需要花费大量的精力确保各处需要修改的地方都被照顾到了。 最简单的情形是代码当中有重复的“常数”为了修改这个常数我们需要多处修改代码。程序员也知道如何解决这一问题例如通过定义个 constant 并处处引用避免 magic number。再例如网页的风格/色彩每个页面相同配置都重复设置同样的色彩和风格是一种模式而采用 css 模版则是更加易于维护的架构。这在架构原则中对应了数据归一化原则Data normalization。 稍微复杂一些的是类似的逻辑/或者功能被 copy-paste 多次原因往往是不同的地方需要稍微不同的使用方式而过去的维护者没有及时 refactor 代码提取公共逻辑这样做往往需要更多的时间精力而是省时间情况下选择了 copy-paste。这就是常说的 Dont repeat yourself 原则 Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. ### 5命名 软件中的 API、方法、变量的命名对于理解代码的逻辑、范围非常重要也是设计者清晰传达意图的关键。然而在很多的项目里我们没有给 Naming /命名足够的重视。 我们的代码一般会和一些项目关联但是需要注意的是项目是抽象的而代码是具体的。项目或者产品可以随意一些命名如阿里云喜欢用中国古代神话飞天、伏羲、女娲命名系统K8s 也是来自于希腊神话这些都没有问题。而代码中的 API、变量、方法不能这样命名。 一个不好的例子是前一段我们的 Cluster API 被命名为 Trident API三叉戟设想一下代码中的对象叫 Trident 时我们如何理解在这个对象应该具备的行为再对比一下 K8s 中的资源Pod, ReplicaSet, Service, ClusterIP我们会注意到都是清晰、简单、直接符合其对象特征的命名。名实相符可以很大程度上降低理解该对象的成本。 有人说“Naming is the most difficult part of software engineering9”或许也不完全是个玩笑话Naming 的难度在于对于模型的深入思考和抽象而这往往确实是很难的。 需要注意的是 **aIntention vs what it is** 需要避免用“是什么”来命名要用“for what / intention”。“是什么”来命名是会很容易将实现细节。比如我们用 LeakedBarrel 做 rate limiting这个类最好叫 RateLimiter而不是 LeakedBarrel前者定义了意图做什么的后者描述了具体实现而具体实现可能会变化。再比如 Cache vs FixedSizeHashMap前者也是更好的命名。 **b命名需要符合当前抽象的层级** 首先我们软件需要始终有清晰的抽象和分层。事实上我们 Naming 时遇到困难很多就是因为软件已经缺乏明确的抽象和分层带来的表象而已。 ### 6不知道一个简单特性需要在哪些做修改或者一个简单的改动会带来什么影响即 unknown unknowns  在所有认知复杂度的表现中这是最坏的一种不幸的是所有人都曾经遇到过这样的情况。 ![8.png](https://ucc.alicdn.com/pic/developer-ecology/daafd132d9f242469786df54888b9c18.png 8.png) 一个典型的 unknown unknowns 是一部分代码存在这样的情况 *   代码缺乏充分的测试覆盖一些重要场景依赖维护者手工测试 *   代码有隐藏 / 不易被发现的行为或者边界条件与文档和接口描述并不符合。 对于维护者来说改动这样的代码或者是改动影响到了这样代码 / 被这样代码影响到了时如果按照接口描述或者文档进行没发现隐藏行为同时代码又缺乏足够测试覆盖那么就存在未知的风险 unknown unknowns。这时出现问题是很难避免的。最好的方式还是要尽量避免我们的系统质量劣化到这个程度。 上线时我们最大的噩梦就是 unknown unknowns这类风险我们无法预知在哪里或者是否有问题只能在软件上线后遇到问题才有可能发现。其他的问题 尚可通过努力来解决认知成本而 unknown unknowns 可以说已经超出了认知成本的范围。我们最希望避免的也是 unknown unknowns。 ### 7认知成本低要不易出错而不是无脑“简化” 从认知成本角度来说我们还要认识到衡量不同方案/写法的认知成本要考虑的是不易出错而不是表面上的简化表面上简化可能带来实质性的复杂度上升。 例如为了表达时间段可以有两种选择 // Time period in seconds. void someFunction(int timePeriod);  // time period using Duration.  void someFunction(Duration timePeriod); 在上面这个例子里面我们都知道应该选用第二个方案即采用 Duration 作 time period而不是 int尽管 Duration 本身需要一点点学习成本但是这个模式可以避免多个时间单位带来的常见问题。 3\. 影响协同成本的因素 ------------- 协同成本则是增长这块模块所需要付出的协同成本。什么样的成本是协同成本 *   增加一个新的特性往往需要多个工程师协同配合甚至多个团队协同配合 *   测试以及上线需要协调同步。 ### 1系统模块拆分与团队边界 在微服务化时代模块/服务的切分和团队对齐更加有利于迭代效率。而模块拆分和边界的不对齐则让代码维护的复杂度增加因这时新的特性需要在跨多个团队的情况下进行开发、测试和迭代。 另外一个角度则是 Any piece of software reflects the organizational structure that produces it. 或者就是我们常说的“组织架构决定系统架构”软件的架构最后会围绕组织的边界而变化当然也有文化因素当组织分工不合理时会产生重复的建设或者冲突。 ### 2服务之间的依赖Composition vs Inheritance / Plugin 软件之间的依赖模式常见的有 Composition 和 Inheritance 模式对于 local 模块/类之间的依赖还是远程调用都存在类似模式。 ![9.png](https://ucc.alicdn.com/pic/developer-ecology/fec1de41cea8498a8fb5321961f9ae11.png 9.png) 上图左侧是 Inheritance继承或者是扩展模式有四个团队其中一个是 Framework 团队负责框架实现框架具有三个扩展点这三个扩展点有三个不同的团队实现插件扩展这些插件被 Framework 调用从架构上这是一种类似于继承的模式。 右侧是组合模式composition底层的系统以 API 服务的方式提供接口而上层应用或者服务通过调用这些接口来实现业务功能。 这两种模式适用于不同的系统模型。当 Framework 偏向于底层、不涉及业务逻辑且相对非常稳定时可以采用 inheritance 模式也即 Framework 被集成到团队 123 的业务实现当中。例如 RPC framework 就是这样的模型RPC 底层实现作为公共的 base 代码 / SDK 提供给业务使用业务实现自己的 RPC 方法被 framework 调用业务无需关注底层 RPC 实现的细节。因为 Framework 代码被业务所依赖因此这时业务希望 Framework 的代码非常稳定而且尽量避免对 framework 层的感知这时 inheritance 是一种比较合适的模型。 然而我们要慎用 Inheritance 模式。Inheritance 模式的常见陷阱 **a要避免出现管理倒置** 即 Framework 层负责整个系统的运维framework 团队负责代码打包、构建、上线那么会出现额外的协同复杂度影响系统演进效率设想一下如果 Dubbo 的团队要求负责所有的使用 Dubbo 的应用的打包、发布成为一个大的应用会是多么的低效。 **b要避免破坏业务逻辑流程的封闭性** Inheritance 模式如果使用不当很容易破坏上层业务的逻辑抽象完整性也即“扩展实现1”这个模块的逻辑依赖于其调用者的内部逻辑流程甚至是内部实现细节这会带来危险的耦合破坏业务的逻辑封闭性。 如果你所在的项目采用了插件 / Inheritance 模式同时又出现上面所说的管理倒置、破坏封闭性情况就需要反思当前的架构的合理性。 而右侧的 Composition 是更常用的模型服务与服务之间通过 API 交互相互解耦业务逻辑的完整性不被破坏同时框架 / Infra 的 encapsulation 也能保证。同时也更灵活在这种模型下Service 1, 2, 3 如果需要也可以产生相互调用。 另外《Effective Java》一书的 Favor composition over inheritance 有很好的分析可以作为这个问题的补充。 ### 3可测试性不足带来的协同成本 交付给其他团队包括测试团队的代码应该包含充分的单元测试具备良好的封装和接口描述易于被集成测试的。然而因为 单测不足/模块测试不足带来的集成阶段的复杂度升高、失败率和返工率的升高都极大的增加了协同的成本。因此做好代码的充分单元测试并提供良好的集成测试支持是降低协同成本提升迭代效率的关键。 可测试性不足带来协同成本升高往往导致的破窗效应上线越来越靠运气unknown unknowns 越来越多。 ### 4文档 降低协同成本需要对接口 / API 提供清晰的、不断保持更新一致的文档针对接口的场景、使用方式等给出清晰描述。这些工作需要投入开发团队有时不愿意投入但是对于每一个用户/使用方需要依赖钉钉上的询问、或者是依靠 ATA 文章多半有 PR 性质或者是已经过时没有及时更新毕竟 ATA 不是产品文档协同成本太高对于系统来说出现 bug / 使用不当的几率大为增加了。 最好的方式 *   代码都公开 *   文档和代码写在一起README.md \*.md随着代码一起提交和更新还计算代码行数多好。 4\. 软件复杂度生命周期 ------------- ![10.png](https://ucc.alicdn.com/pic/developer-ecology/a3dd7c2c7d7d4c4da54237ecbbd5b6a1.png 10.png) 复杂度的恶化到一定程度一定进入有诸多 unknown unknowns 的程度。好的工程师一定要能识别这样的状态可以说如果不投入力气去做一定的重构/改造有过多 unknown unknowns 的系统很难避免失败的厄运了。 这张图是要表明软件演进的过程是一个“不由自主”就会滑向过于复杂而无法维护的深渊的过程。如何要避免失败的厄运这篇文章的篇幅不容许我们展开讨论如何避免复杂度但是首要的对于真正重要的、长生命周期的软件演进我们需要做到对于复杂度增量零容忍。 5\. Good enough vs Perfect -------------------------- 软件领域从效率和质量的折中我们会提“Good enough”即可。这个理论是没错的。只不过现实中我们极少看到“overly good”因为过于追求 perfection 而影响效率的情况。大多数情况下我们的系统是根本没做到 Good enough。 对复杂度增长的对策 每一份新的代码的引入都在增加系统的复杂度因为每一个类或者方法的创建都会有其他代码来引用或者调用这部分代码因而产生依赖/耦合增加系统的复杂度除非之前的代码过度复杂 unncessarily complex而通过重构可以降低复杂度如果读者都意识到了这个问题并且那些识别增加复杂度的关键因素对于大家有所帮助那么本文也就达到了目标。 而如何 Keep it simple是个非常大的话题本文不会展开。对于 API 设计在 API 设计最佳实践思考中做了一些总结其他的希望后续有时间能继续总结。 API 设计最佳实践思考[https://developer.aliyun.com/article/701810](https://developer.aliyun.com/article/701810) 有人会说项目交付的压力才是最重要的不要站着说话不腰疼。实际呢我认为绝对不是这样。多数情况下我们要对复杂度增长采用接近于“零容忍”的态度避免“能用就行”原因在于 *   复杂度增长带来的风险unknown unknowns、不可控的失败等往往是后知后觉的等到问题出现时往往 legacy 已经形成一段时间或者坑往往是很久以前埋的 *   当我们在代码评审、设计评审时面临一个个选择时每一个 Hack、每一个带来额外成本和复杂度的设计似乎都显得没那么有危害就是增加了一点点复杂度而已就是一点点风险而已。但是每一个失败的系统的问题都是这样一点点积累起来的 *   破窗效应 Broken window一个建筑当有了一个破窗而不及时修补这个建筑就会被侵入住认为是无人居住的、风雨更容易进来更多的窗户被人有意打破很快整个建筑会加速破败。这就是破窗效应在软件的质量控制上这个效应非常恰当。所以Dont live with broken windows (bad designs, wrong decisions, poor code) 有破窗尽快修。 零容忍并不是不让复杂度增长我们都知道这是不可能的。我们需要的是尽力控制。因为进度而临时打破窗户也能接受但是要尽快补上。 当然文章一开始就强调了如果所写的业务代码生命周期只有几个月那么多半在代码变得不可维护之前就可以下线了那可以不用关注太多能用就行。 最后作为 Software engineer软件是我们的作品希望大家都相信 *   真正的工程师一定在意自己的作品我们的作品就是我们的代码。工匠精神是对每个工程师的要求 *   我们都可以带来改变代码是最公平的工作场地代码就在那里只要我们愿意就能带来变化。 云原生微服务框架哪家强 近期阿里云原生团队联合 X-lab 开放实验室发布《2020 年微服务领域开源数字化报告》通过 2020 年 1 月到 6 月的 GitHub 日志进行统计分析微服务框架项目以及 Spring Cloud 项目的 GitHub 开发者行为日志分析了开源微服务框架活跃度。 [原文链接](https://developer.aliyun.com/article/771010?utm_contentg_1000175546) 本文为阿里云原创内容未经允许不得转载。
http://www.pierceye.com/news/3230/

相关文章:

  • 微网站预览外贸网站怎样注册
  • 商城网站开发教程视频wordpress页面文本编辑评论
  • 陕西住房与城乡建设部网站男女的做那个视频网站
  • 蚌埠做网站哪家好网络维护培训班
  • custed谁做的网站网站建设服务要交印花税吗
  • 工程建设项目网站中小型网站建设如何
  • 跨境电子商务网站建设网站设计需要那些人
  • 远程管理wordpress站群河南省建设教育培训中心网站
  • 网站后台安全性配置阳江营销网站开发
  • 快速网站推广首页排名网站主体备案信息查询
  • 一个网站同时做百度和360推广吗网站排名站长之家
  • 自学网站官网重庆沙坪坝网站建设
  • 网站建设乐云seo金华外贸网站建设
  • 建立企业门户网站建设大连seo外包公司
  • 广州网站建设在线深圳燃气公司招聘信息
  • 成都哪里做网站好手机网站建设价格
  • 改变网站的域名空间wordpress pages
  • 新闻静态网站模板无人区高清免费网页直播
  • 零基础自己建网站网络营销策划目的
  • 来年做啥网站能致富网上拿手工做的网站
  • 做新网站推广的活动网站建设的文件
  • 酒类网站建设方案案代做道具网站
  • 上海空灵网站设计网站建设 知乎
  • 企业网站优化案例点赞排行 wordpress
  • 建设网站需要提供什么资料长三角旅游推广联盟
  • 做网站需要学那几个软件深圳软件有限公司
  • 未来做啥网站能致富企业网站免费源码
  • 网站 维护 费用北京vi设计
  • 成都网站建设开发公司哪家好在线网站建设者
  • 网站推广应注意哪些事项网站建设的关键要素