系统渐渐沦为“屎山”,这就是真相!

news2024/11/16 17:33:27

分享是最有效的学习方式。

博客:https://blog.ktdaddy.com/

背景

小猫维护现有的系统也有一段时间了,踩坑也不少,事故不少。感兴趣的小伙伴可以了解一下,往期的小猫踩坑记合集。

这天,小猫找到了商城系统的第一任开发老A开始聊天。

“你们这系统是真坑,我都吃过好多次亏了,太烂了…”小猫开玩笑地吐槽道。

“我们当时其实还是花了很长的一段时间去做设计以及评审的,每一步都是有严格把控的,当时系统总体骨架还是相当清晰的,也没有那么多新的概念,你说现在系统不行了,可不能怪我们啊。一会我给你原始设计文档看看。后面经过了很多研发的手了,甚至运营和产品都换了好几拨了,每任运营可能对当前系统的要求都不一样吧,大家理解可能都不同…”老A侃侃而言着。

小猫抿了口刚倒的茶,意味深长地看着老A…

写在前面

相信有很多小伙伴都有小猫这样的体会,尤其是接手一个老的系统的时候,总是会吐槽当前的系统很烂,恨不得马上将其完完全全重构掉。

前段时间老猫还遇到一个比较逗的小伙伴,他想表达的意思大概是“代码写的烂也就算了,他居然还在注释里撒谎…”,结果他楼下哥们还在一个劲追问他的注释是怎么撒谎的,老猫当时边吃午饭边在刷手机,老猫看到评论后,笑到喷饭,当然在此也对这位小伙伴表示同情。

在这里插入图片描述

其实很多时候一个系统的腐烂和破败并不是在开始的时候就出现了,而是在持续地迭代升级中渐渐腐化继而沦为“屎山”。

接下来咱们就来盘点一下到底是一些什么原因将一个原本架构清晰的系统腐败沦丧为复杂“屎山”的,然后咱们作为后来人又该如何应对?

在这里插入图片描述

“屎山”特征

既然说到系统沦为“屎山”,那么什么样的系统会被定义成“屎山”呢?其实所谓的“屎山”即为非常复杂的系统,难以维护。John OusterhOut在A Philosophy of Software Design这本书中就已经提及了“复杂性就是使得软件难于理解和修改的因素”。

John OusterhOut将“屎山”(这里要说明一下,这哥们并没有说复杂系统叫“屎山”,哈哈,只是咱们都习惯这么叫了)归为三大类:

  • Change Amplification(变更放大)
  • Cognitive Load(认知负荷)
  • Unknown Unknowns(未知的未知)

上述几类特种总结有点抽象,咱们来具体化阐述一下。

变更放大

变更放大其实就是说明明一个相当简单的需求,却要动到很多地方的代码。

相信大家在日常开发中应该也会遇到这样的问题。老猫也经常遇到,例如明明从需求的理解来说,只要加一个固定的校验逻辑,这个事情就应该可以搞定了,结果发现,改一个地方的校验逻辑还不行,可能还要动到各个地方的校验逻辑。这种情况往往是由于开发在写代码过程中复用没有到位,或者本身流程问题导致的。软件可拓展性变得很差。

认知负荷

认知负荷说白了就是相关的开发人员在进行开发任务的时候,需要花费很多时间去学习所需要的知识(当然这里大部分指的是技术知识)才能完成一系列开发任务,于此同时,如果某个知识点没有掌握好,可能会导致未知的Bug。

打个比方,大部分的开发还是比较倾向于自己熟悉的编程语言或者是开发框架,以及中间件的。例如,前后端分离虽然好,DDD虽然好,但是对于简单的内部管理系统而言,明明一个mvc就能搞定的事情,非得搞成前后端分离,加上DDD设计分层。明明一个人天就能搞定的事情,非得搞成三人天,另外的维护者可能还得花时间去研究相关技术,这种盲目追求最新技术增加系统本身实现复杂度就是一种本末倒置的行为。

当然认知负荷有的时候可能也不一定是新技术带来的,也有可能是纯粹的技术实现烂,例如不恰当的接口设计、混乱的命名,还有“爱撒谎”的注释等等。

未知的未知

未知的未知是最要命的,例如,当我们从产品那边得到一个需求的时候,我们甚至不晓得为了完成这个需求我们到底需要修改哪些代码才能完成,当前开发甚至还不清楚相关的业务知识。

这种很多时候体现在咱们接手一个新项目的时候,尤其是项目比较复杂的情况下,并且此时的项目没有任何的技术文档,这种情况下我们往往是抓瞎的,非常被动,即使对代码调整完毕之后心里也还是会没底,涉及的一些业务场景甚至都没有理清楚。也不晓得调整完毕之后会不会出现新的问题。

“屎山”诱因

从技术侧聊聊,复杂系统发展的诱因,UML之父Grady Booch在《面向对象分析和设计》的观点是,软件的复杂性是一个固有属性,并不是偶然属性,软件的发展必然会伴随复杂性。

诱因有下面三个:

  1. 模糊性:模糊性产生了最直接的复杂度。
    老猫的理解,关于模糊性包含其实有两层,一个是需求模糊性,第二个技术模糊性。产品经理对实际的业务把控没有做到很精准,存在模糊性,导致系统本身的业务覆盖点经常发生变更,这种是导致系统复杂的罪魁祸首之一。第二技术侧的模糊性,技术侧的模糊性当然就包含研发人员本身对业务把控不到位,另外的在定义API以及方法命名变量命名的时候存在模糊,无法通过命名直接理解想要表达的意思。

  2. 依赖性:模糊产生复杂性,而依赖导致复杂的传递,不断外移的复杂性将导致最终系统无限腐化,质量失控,修复成本指数级增长。打个比方一个不合理的实现方法被我们认为是一套标准的实现方式,然后后面的很多业务代码为了方便都会去复用这段逻辑。但是这种不合理的实现方法还在不停的迭代,所以之后系统会发展成什么样子,大家可想而知了。

  3. 递增性:一个软件系统无论多么复杂,都是从第一行代码开始的。然后慢慢“生长”。随着业务发展,需求不断产生,功能逐渐丰富,软件系统随之演进,同时废弃而未被及时清除的代码也是日益膨胀。最终形成一个复杂的系统。
    这点相信理解起来还是比较简单的。

系统“腐烂”的真相

就像上面小猫和老A的对话那样,其实很多时候,系统的腐烂并并不是发生在最开始。

很多后端研发在接手新的系统之后,往往对其设计的理解其实是不够深入的,来了需求之后就是一顿“兵来将挡水来土掩”,可以说是一种战术性编程,或者说的难听些“应付式编程”。

这种编程的特点有下面这几种:

  1. 快。这类程序员为了快速解决产品需求,总是以腐化系统为代价去解决问题。经过他们之手维护的系统可拓展性差。
  2. 高产。这类程序员代码量极大,可能不择手段,完全不会考虑复用,很多时候解决问题就是cv大法。
  3. 坑。他们往往只是专注于功能堆砌却忽略设计原则和设计规范,有时候命名规范甚至都懒得遵循,成本放到未来,后人买单。咱们经常提到的倒霉的小猫就是经常买单的那位。

上述共同特点就是缺乏设计,完全聚焦于快速交付,注重短期价值不考虑未来发展。

那么为什么会这样的呢?可能会受到以下三点的影响:

  1. 研发人员本身的水平以及认知还有责任心。研发人员本身认知不够,意识不到系统其实是需要考虑拓展性的,这种往往也是没有办法的,另外一种是研发人员抱有侥幸心理,虽然意识到拓展性的问题以及设计问题,但是比较懒,本着“多一事不如少一事,反正我只是过客”的心态去做系统。这类往往在外包系统中体现更为放大。

  2. 互联网背景下,老板为了快速适应市场,会进行大量业务试错,这就会要求程序员快速开发。很多程序员想要好好设计一下系统,可是无奈妥协于项目经理的一而再再而三的问你上线时间。这种情况下,设计可能就成了一种奢侈。

  3. 考评体系不合理。老猫有个朋友,之前一天他和我们吐槽,他们目前领导需要拉出他们每天写代码的量去看看他们每天干了多少活。这种真的是滑天下之大稽,在这样的考评体系下面,程序员还会好好写代码么。当然这种往往是发生在领导屁都不懂研发的公司。这类领导也是老猫最最鄙视的。技术上明明屁都不懂,还要装x去指指点点。

在这里插入图片描述

“屎山”应对之道

上面聊了这么多,我们也大概知道了为什么我们的系统会逐渐沦为“屎山”,可能是在软件发展过程中的必然,其中也掺杂着各种人为因素以及非人为因素。
当然事情还是要去解决的。那么我们应当如何应对呢?

  • 寻找合适的架构

    当咱们接到一个复杂系统的时候,其实首先需要理清楚相关的架构,知道系统是如何进行模块拆分的,另外它们的协作关系和通信方式。具体操作,大家可以访问老猫之前写的系统梳理大法

  • 遵循设计原则
    组件层面,咱们的设计原则需要遵循复用/发布等同原则,共同闭包原则,共同复用原则,无依赖环原则,稳定依赖原则和稳定抽象原则。
    代码层面,可以参考老猫之前梳理的开发中需要遵循的设计原则。

  • 避免破窗效应

    这里的“破窗效应”其实是出自David Thomas Andrew Hunt的著作《程序员修炼之道》,一扇破窗,只要一段时间不去修理,建筑中的居民就会潜移默化地产生一种被遗弃的感觉————当权者不关心这幢建筑。然后其他窗户也开始损坏,居民开始乱丢废物,墙上开始乱涂鸦,建筑开始出现严重结构性的损坏。

    聊到咱们软件系统侧其实也是一样的,在系统发展的过程中,只有在我们修复历史遗留的问题时,才是真正对其进行了维护。如果我们使用一些极端的手段保持古老陈腐的代码继续工作的时候,这其实是一种苟且。例如为了临时解决问题写hotfix接口等等。

    在我们开发的过程中,一旦系统有了设计缺陷,咱们其实应该及时优化,否则会形成不好的示范,更多的后来者会倾向于做出类似设计,从而加速系统腐化。

总结

上述就是老猫对系统沦为“屎山”的一些看法,另外的,希望大家比较再提“防御性编码”这类概念。这种思想就不应该是一个合格程序员提出的。老猫对这类还是比较抵触的。“难不成螺丝钉以为自己螺纹角度独特就不会被取代了?”,咱们把自己负责的东西尽量做到完美,是金子总能发光的,小伙伴们,你们觉得呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1526682.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构与算法】:非递归实现快速排序、归并排序

🔥个人主页: Quitecoder 🔥专栏:数据结构与算法 上篇文章我们详细讲解了递归版本的快速排序,本篇我们来探究非递归实现快速排序和归并排序 目录 1.非递归实现快速排序1.1 提取单趟排序1.2 用栈实现的具体思路1.3 代码…

掘根宝典之C++RTTI和类型转换运算符

什么是RTTI RTTI是运行阶段类型识别的简称。 哪些是RTTI? C有3个支持RTTI的元素。 1.dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针,否则该运算符返回0——空指针。 2.typeid运算符返回一个指出对象类型的信息 3.type_info结构存储…

【鸿蒙HarmonyOS开发笔记】如何使用图片插帧将低像素图片清晰放大

开发UI时,当我们的原图分辨率较低并且需要放大显示时,图片会模糊并出现锯齿。如下图所示 这时可以使用interpolation()方法对图片进行插值,使图片显示得更清晰。该方法的参数为ImageInterpolation枚举类型,可选的值有: ImageInte…

通过点击按钮实现查看全屏和退出全屏的效果

动态效果如图&#xff1a; 可以通过点击按钮&#xff0c;或者esc键实现全屏和退出全屏的效果 实现代码&#xff1a; <template><div class"hello"><el-button click"fullScreen()" v-if"!isFullscreen">查看全屏</el-butt…

centos创建并运行一个redis容器 并支持数据持久化

步骤 : 创建redis容器命令 docker run --name mr -p 6379:6379 -d redis redis-server --appendonly yes 进入容器 : docker exec -it mr bash 链接redis : redis-cli 查看数据 : keys * 存入一个数据 : set num 666 获取数据 : get num 退出客户端 : exit 再退…

猫头虎分享已解决Bug || TypeError: Cannot interpret ‘float‘ value as integer.

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

luceda ipkiss教程 62:等长波导布线(二)

教程 27介绍了两段波导等长布线的例子&#xff0c;下面同样是通过控制偏移量实现三段波导的等长布线&#xff1a; 所有代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3class demo(i3.Circuit):mmi i3.ChildCellProperty(doc"mmi in…

数据导入--Insert into

Insert Into是我们在MySQL中常用的导入方式&#xff0c;StarRocks同样也支持使用Insert into的方式进行数据导入&#xff0c;并且每次insert into操作都是一次完整的导入事务。 在StarRocks中&#xff0c;Insert的语法和MySQL等数据库的语法类似&#xff0c;具体可以参考官网文…

苹果谷歌,要联手反攻了

一则消息&#xff0c;让苹果、谷歌的夜盘股价一度分别暴拉1.5、3.5%&#xff0c;谷歌盘前甚至飙升超过5.5%&#xff0c;引发市场一阵轰动。 据知情人士透露&#xff0c;苹果公司正在谈判将谷歌的Gemini人工智能引擎植入iPhone&#xff0c;希望获得Gemini的授权&#xff0c;为今…

【办公类-22-11】周计划系列(5-3)“周计划-03 周计划内容循环修改“ (2024年调整版本)

背景需求&#xff1a; 前文从原来的“新模版”文件夹里提取了周计划主要内容和教案内容。 【办公类-22-10】周计划系列&#xff08;5-2&#xff09;“周计划-02源文件docx读取5天“ &#xff08;2024年调整版本&#xff09;-CSDN博客文章浏览阅读1.1k次&#xff0c;点赞29次&…

全基因集GSEA富集分析

原文链接&#xff1a;一文完成全基因集GSEA富集分析 本期内容 写在前面 我们前面分享过一文掌握单基因GSEA富集分析的教程&#xff0c;主要使用单基因的角度进行GSEA富集分析。 我们社群的同学咨询&#xff0c;全基因集的GSEA如何分析呢&#xff1f;&#xff1f;其实&#x…

利用自定义 URI Scheme 在 Android 应用中实现安全加密解密功能

在现代移动应用开发中&#xff0c;安全性和用户体验是至关重要的考虑因素。在 Android 平台上&#xff0c;开发人员可以利用自定义 URI Scheme 和 JavaScript 加密解密技术来实现更安全的数据传输和处理。本文将介绍如何在 Android 应用中注册自定义 URI Scheme&#xff0c;并结…

C语言例:整型常量025,求解十进制和十六进制

1. 八进制数的每一位乘以对应的权值&#xff08;8的幂&#xff09;&#xff0c;然后将结果相加&#xff0c;得到十进制数。 025 21 2.八进制先转二进制&#xff08;一变三&#xff09;&#xff0c;再二进制转十六进制&#xff08;四合一&#xff09; 025 0001 0101 0…

25双体系Java学习之StringBuffer和StringBuilder

StringBuffer和StringBuilder ★小贴士 String str new String("welcome to "); str "here"; 字符串的拼接过程实际上是通过建立一个StringBuffer&#xff0c;然后调用StringBuffer的append方法&#xff0c;最后再将StringBuffer转为字符串&#xff0c…

第四百一十回

文章目录 1. 概念介绍2. 方法与细节2.1 获取方法2.2 使用细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取当前系统语言"相关的内容&#xff0c;本章回中将介绍如何获取时间戳.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

ElasticSearch架构设计

一、基础概念 Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene™ 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单&#xff0c;它不仅包括了全文搜索功能&#xff0c;还可以进行以下工作: 一个分布式的实时文档…

【Linux】深入了解Linux磁盘配额:限制用户磁盘空间的利器

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 前言 在多用户环境下管理磁盘空间是服务器管理中的一项重要任务。Linux提供了强大的磁盘配额功能&#xff0c;可以帮助管理员限制用户或组对文件系统…

【趣味项目】命令行图片格式转换器

【趣味项目】一键生成LICENSE 项目地址&#xff1a;GitHub 项目介绍 一款命令行内可以批量修改图片格式的工具 使用方式 npm install xxhls/image-transformer -gimg-t --name.*.tiff --targetpng --path./images --recursiontrue技术选型 typeScript: 支持类型体操chal…

你开发的系统国际化了吗?

亲爱的朋友们&#xff0c;周一好&#xff0c;新的一周&#xff0c;精神满满。 在开发Spring Boot应用时&#xff0c;接口的参数校验是一个重要的环节&#xff0c;它确保了数据的完整性和准确性。而国际化处理则使得应用能够支持多种语言&#xff0c;提升了用户体验。 一、参数…

vue中判断是否使用自定义插槽

在封装自定义组件时&#xff0c;需要判断使用者是否使用了插槽<slot"aaa">&#xff0c;如果没有则使用一个组件中默认的值&#xff0c;反之就用传入的内容<template name"aaa"></template>,实现如下&#xff1a; <div class"lin…