」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 與軟體複雜性的永無止境的鬥爭

與軟體複雜性的永無止境的鬥爭

發佈於2024-08-18
瀏覽:453

The Never Ending Battle Against Software Complexity

什么是复杂性?

最近读完了《软件设计哲学》,第二章探讨了软件复杂性的话题。 

《软件设计哲学》一书实用地定义了复杂性:

“复杂性是与软件系统的结构相关的任何使其难以理解和修改的事物。”

换句话说,复杂性可以有多种形式,并且不一定与性能有任何关系,您的代码可以具有高性能但仍然很复杂

我想在本文中分享本书中的一些关键定义和见解。但首先,让我们想象一个您可能已经经历过的常见情况……


一个简短的恐怖故事

让我们深入探讨一个你们中的许多人可能经历过或将要经历的恐怖故事。

  1. 它从一个简单的 CRUD 任务管理应用程序开始。代码干净、模块化且易于维护。开发团队很高兴,系统对于最初的客户来说运行得很好。

  2. 当销售团队将系统出售给一家大公司时,问题就开始了,声称它具有日历集成、电子邮件通知和令人惊叹的报告生成器。销售完成后,必须快速实施这些功能。

  3. 日历集成: 团队必须与 Google 日历和 Outlook 集成。不同的开发人员实施了解决方案,导致方法不一致。

  4. 电子邮件通知: 接下来添加电子邮件通知。一位开发人员使用特定的库,而另一位开发人员创建了自定义解决方案。混合方法使代码变得混乱。

  5. 报告生成器: 对于报告生成器,开发人员使用了各种技术:PDF、Excel 导出和交互式仪表板。缺乏统一的方法使维护成为一场噩梦。

  6. 复杂性不断增加:每个功能都是独立快速开发的,导致功能之间存在依赖关系。开发人员开始创建“快速修复”以使一切正常运行,从而增加了系统的复杂性和耦合性。

软件开发不是在真空中进行的;而是在现实中进行的。各种内部和外部因素都会对其产生影响。我们都曾经历过或将会经历过这样的情况。


结束的开始

然后问题开始了:

  1. 系统某一部分的变化意外地影响了其他部分。
  2. 小改动需要修改许多其他文件,使得估计变得困难。
  3. 月复一月,代码变得越来越难以理解,通常通过反复试验来修复。
  4. 生产力下降,每个人都害怕维护任务。
  5. 不可避免地呼吁“我们需要重构。”
  6. 某些任务只能由特定的开发人员处理(经典)
  7. 随着时间的推移,曾经编写精美且文档齐全的软件变成了火车残骸。

命名症状

很明显,我们现在拥有一个复杂的系统。

现在让我们“剖析”这种复杂性,以便更容易识别和减轻它。

嗯,“缓解”的意思是:

“减轻严重性、严重性或痛苦;减轻。”

我相信复杂性通常是代码所固有的。有些事情本质上是复杂的。作为开发人员,您的角色不仅仅是创建计算机可以高效执行的代码,还要创建未来的开发人员(包括未来的您)可以使用的代码。

“控制复杂性是计算机编程的本质。”

— 布莱恩·科尼汉

上述书籍的作者指出,复杂性通常以三种方式表现出来,我们将在这里探讨。

改变放大

当看似简单的更改需要在许多不同的地方进行修改时,就会发生更改放大。

例如,如果产品负责人请求“优先级”或“完成日期”字段,并且您的实体紧密耦合,那么您需要进行多少更改?

认知负荷

认知负荷是指开发人员完成任务所需的知识量和时间。

想象一下这样的场景:一位新开发人员加入了团队,他被指派修复报告生成器中的错误。为了完成此任务,开发人员需要:

  • 了解不同的日历集成(Google 和 Outlook)。
  • 掌握电子邮件通知的不同方法。
  • 浏览报告生成器的碎片代码,处理 PDF、Excel 和仪表板。
  • 整合这些不同的技术和风格来查找并修复错误。

这是典型的“无法估计”场景,任务可能需要 1 分或 8 分——最好掷 D20 并做出相应的反应。

未知的未知

未知的未知是指你不知道自己不知道的事情。

这是复杂性的最糟糕表现,因为你可能会改变不应该改变的东西,导致一切崩溃。

示例:开发人员修改了电子邮件发送代码以添加新通知,但没有意识到这会影响依赖于该函数的报告生成器。这给客户带来了重大问题,体现了紧急复杂性的最坏形式。

复杂性的原因

看完恐怖故事和三个主要症状,让我们看看是什么导致了复杂性。

1. 依赖关系

依赖关系在软件中至关重要,无法完全消除。它们允许系统的不同部分相互作用并一起运行。然而,如果管理不当,依赖关系会显着增加复杂性。

定义:

当代码无法单独理解或修改时,就存在依赖关系,需要考虑或修改相关代码。

依赖项类型:

  • 直接:模块A直接依赖模块B。
  • 传递性:模块A依赖于模块B,模块B又依赖于模块C。
  • 循环:模块A、B、C以循环方式相互依赖。

2. 默默无闻

当重要信息不明显时,就会出现模糊性。这可能会使代码库难以理解,从而导致认知负荷增加和未知未知的风险。

定义:

当重要信息不明显时,就会出现模糊性。

晦涩难懂的例子:

  • 命名不当:名称不明确的变量和函数。
  • 隐藏的副作用:执行意外操作的方法。
  • 全局状态: 过度使用全局变量。
  • 深层继承:行为分布在类层次结构中的多个级别。

请记住:复杂性是渐进的

  • 复杂性很少是由单个“错误”或错误决策引起的。
  • 随着时间的推移,由于错误的决策和依赖性,复杂性“缓慢”地增加。

因为它是增量的,所以很容易想到,“就这一次,没关系。”但积累起来后,仅修复一两个依赖项不会产生太大影响。

“软件工程中的一切都是权衡。”
——我不记得作者了

结论

我可以写很多你可能已经在互联网上看到的关于如何避免复杂性的规则、策略和框架:SOLID、设计模式、YAGNI、KISS 等。

但是,您可以将它们全部统一为一个指导原则(如“务实的程序员”中提到的。): “我正在实现的内容容易更改吗?” 如果答案是否定的,那么您可能正在增加复杂性。

确保您的代码易于更改可以简化维护,减少开发人员的认知负担,并使系统更具适应性且不易出错。

谢谢你!

版本聲明 本文轉載於:https://dev.to/juniorklawa/the-never-ending-battle-against-software-complexity-46k1?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 在Pandas中如何將年份和季度列合併為一個週期列?
    在Pandas中如何將年份和季度列合併為一個週期列?
    pandas data frame thing commans date lay neal and pree pree'和pree pree pree”,季度 2000 q2 這個目標是通過組合“年度”和“季度”列來創建一個新列,以獲取以下結果: [python中的concate...
    程式設計 發佈於2025-06-09
  • 如何在php中使用捲髮發送原始帖子請求?
    如何在php中使用捲髮發送原始帖子請求?
    如何使用php 創建請求來發送原始帖子請求,開始使用curl_init()開始初始化curl session。然後,配置以下選項: curlopt_url:請求 [要發送的原始數據指定內容類型,為原始的帖子請求指定身體的內容類型很重要。在這種情況下,它是文本/平原。要執行此操作,請使用包含以下標頭...
    程式設計 發佈於2025-06-09
  • \“(1)vs.(;;):編譯器優化是否消除了性能差異?\”
    \“(1)vs.(;;):編譯器優化是否消除了性能差異?\”
    答案: 在大多數現代編譯器中,while(1)和(1)和(;;)之間沒有性能差異。編譯器: perl: 1 輸入 - > 2 2 NextState(Main 2 -E:1)V-> 3 9 Leaveloop VK/2-> A 3 toterloop(next-> 8 last-> 9 ...
    程式設計 發佈於2025-06-09
  • 解決Spring Security 4.1及以上版本CORS問題指南
    解決Spring Security 4.1及以上版本CORS問題指南
    彈簧安全性cors filter:故障排除常見問題 在將Spring Security集成到現有項目中時,您可能會遇到與CORS相關的錯誤,如果像“訪問Control-allo-allow-Origin”之類的標頭,則無法設置在響應中。為了解決此問題,您可以實現自定義過濾器,例如代碼段中的MyFi...
    程式設計 發佈於2025-06-09
  • 如何解決AppEngine中“無法猜測文件類型,使用application/octet-stream...”錯誤?
    如何解決AppEngine中“無法猜測文件類型,使用application/octet-stream...”錯誤?
    appEngine靜態文件mime type override ,靜態文件處理程序有時可以覆蓋正確的mime類型,在錯誤消息中導致錯誤消息:“無法猜測mimeType for for file for file for [File]。 application/application/octet...
    程式設計 發佈於2025-06-09
  • C++20 Consteval函數中模板參數能否依賴於函數參數?
    C++20 Consteval函數中模板參數能否依賴於函數參數?
    [ consteval函數和模板參數依賴於函數參數在C 17中,模板參數不能依賴一個函數參數,因為編譯器仍然需要對非contexexpr futcoriations contim at contexpr function進行評估。 compile time。 C 20引入恆定函數,必須在編譯時進...
    程式設計 發佈於2025-06-09
  • 在PHP中如何高效檢測空數組?
    在PHP中如何高效檢測空數組?
    在PHP 中檢查一個空數組可以通過各種方法在PHP中確定一個空數組。如果需要驗證任何數組元素的存在,則PHP的鬆散鍵入允許對數組本身進行直接評估:一種更嚴格的方法涉及使用count()函數: if(count(count($ playerList)=== 0){ //列表為空。 } 對...
    程式設計 發佈於2025-06-09
  • Python環境變量的訪問與管理方法
    Python環境變量的訪問與管理方法
    Accessing Environment Variables in PythonTo access environment variables in Python, utilize the os.environ object, which represents a mapping of envir...
    程式設計 發佈於2025-06-09
  • 您可以使用CSS在Chrome和Firefox中染色控制台輸出嗎?
    您可以使用CSS在Chrome和Firefox中染色控制台輸出嗎?
    在javascript console 中顯示顏色是可以使用chrome的控制台顯示彩色文本,例如紅色的redors,for for for for錯誤消息? 回答是的,可以使用CSS將顏色添加到Chrome和Firefox中的控制台顯示的消息(版本31或更高版本)中。要實現這一目標,請使用以下...
    程式設計 發佈於2025-06-09
  • 如何避免Go語言切片時的內存洩漏?
    如何避免Go語言切片時的內存洩漏?
    ,a [j:] ...雖然通常有效,但如果使用指針,可能會導致內存洩漏。這是因為原始的備份陣列保持完整,這意味著新切片外部指針引用的任何對象仍然可能佔據內存。 copy(a [i:] 對於k,n:= len(a)-j i,len(a); k
    程式設計 發佈於2025-06-09
  • Java為何無法創建泛型數組?
    Java為何無法創建泛型數組?
    通用陣列創建錯誤 arrayList [2]; JAVA報告了“通用數組創建”錯誤。為什麼不允許這樣做? 答案:Create an Auxiliary Class:public static ArrayList<myObject>[] a = new ArrayList<my...
    程式設計 發佈於2025-06-09
  • Python讀取CSV文件UnicodeDecodeError終極解決方法
    Python讀取CSV文件UnicodeDecodeError終極解決方法
    在試圖使用已內置的CSV模塊讀取Python中時,CSV文件中的Unicode Decode Decode Decode Decode decode Error讀取,您可能會遇到錯誤的錯誤:無法解碼字節 在位置2-3中:截斷\ uxxxxxxxx逃脫當CSV文件中包含特殊字符或Unicode的路徑...
    程式設計 發佈於2025-06-09
  • 如何使用替換指令在GO MOD中解析模塊路徑差異?
    如何使用替換指令在GO MOD中解析模塊路徑差異?
    在使用GO MOD時,在GO MOD 中克服模塊路徑差異時,可能會遇到衝突,其中3個Party Package將另一個PAXPANCE帶有導入式套件之間的另一個軟件包,並在導入式套件之間導入另一個軟件包。如迴聲消息所證明的那樣: go.etcd.io/bbolt [&&&&&&&&&&&&&&&&...
    程式設計 發佈於2025-06-09
  • 如何在Java字符串中有效替換多個子字符串?
    如何在Java字符串中有效替換多個子字符串?
    在java 中有效地替換多個substring,需要在需要替換一個字符串中的多個substring的情況下,很容易求助於重複應用字符串的刺激力量。 However, this can be inefficient for large strings or when working with nu...
    程式設計 發佈於2025-06-09
  • Go語言垃圾回收如何處理切片內存?
    Go語言垃圾回收如何處理切片內存?
    Garbage Collection in Go Slices: A Detailed AnalysisIn Go, a slice is a dynamic array that references an underlying array.使用切片時,了解垃圾收集行為至關重要,以避免潛在的內存洩...
    程式設計 發佈於2025-06-09

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3