发布于 2026-01-06 0 阅读
0

为什么编程会如此复杂得离谱?

为什么编程会如此复杂得离谱?

别误会,这并不……只是比它应该有的复杂得多。

我目前正在编写一个框架,并决定以正确的方式进行开发……你知道的,比如选择编程语言(以及版本……)、选择合适的编程范式、进行测试驱动开发(TDD)、创建持续集成/持续交付(CI/CD)流水线(提交 PR 时自动触发,也可以在本地运行)(哦,当然还有设置 GitHub/Azdo/Bitbucket/等等)、选择许可证,以及可能还有很多我都没意识到的事情。唯一我还没考虑的(目前为止……)是代码覆盖率。而所有这些都是在编写第一行代码之前完成的。

然后,完全无关的事情发生了,我写了以下脚本:

$ vim ~/bin/aur

#!/bin/bash

git pull

makepkg -i
Enter fullscreen mode Exit fullscreen mode

我一直在想,这写得多么优雅易读……如果我按正确的方式去做,我可能会把它搞砸

免责声明

坦白说,这篇文章没有任何意义。它只是提出了一大堆问题,或许还有一些解决方案……或者更确切地说,是想请读者(没错,就是你)提出解决方案,并希望大家能分享出来。

我们为什么要做这一切?

说实话,软件工程(也就是编程)简直是一团乱麻,但入门却如此容易(Hello, World!),以至于人人都觉得自己能搞定……结果就觉得自己比谁都懂。甚至有些从未写过一行代码的人都在指导开发人员如何工作。这或许就是我们为什么需要建立如此复杂的工具、实践和流程体系的第一个原因……

问责制

如果出了问题,那是开发人员的错……而不是产品负责人编写了一个敷衍了事的功能,也不是测试人员没有测试百万分之一概率的场景(因为他/她没时间测试)……更不是CEO不想在核心业务上多花钱。

没错,软件如今就是你的独特卖点!想要在竞争中脱颖而出(至少在大多数行业是这样),唯一的办法就是拥有合适的工具;如果这些工具不存在,你就得自己开发。你可以拥有世界上最好的产品/服务,但如果没有相应的软件支持,你的竞争对手会瞬间超越你。话虽如此,如果我们能以更低的成本做到这一点,岂不是更好?或者至少更智能地做到这一点,因为否则竞争对手也能做到……

总之,我们都要对我们开发的软件负责,但我们也需要能够对它负责。那么,我们如何才能对一个不断发展、复杂到我们甚至无法理解可能出错之处的软件负责呢?

多年来,我们尝试了各种各样的方法(ASDCI/CDDDDFPXP等等,不胜枚举),试图解决我们面临的问题。但这些问题究竟是什么?为什么这些解决方案也会带来同样多的新问题?为什么我需要理解这么多乱七八糟的东西才能写出一行代码?

流动性

人们通常容易忽略的最大问题可能是软件的流动性。建造一栋(实体)房子,一旦完工,基本上就定型了。你不会第二天就决定在顶楼加个游泳池……就算你真这么做了,至少也会有20个人告诉你,虽然这主意很棒……但真是蠢透了。然而,在软件中,加个游泳池的后果并非是它会坍塌,导致大楼里所有人丧命。它会稍微削弱基础,但嘿,多一点技术债务又有什么关系呢?

然而,这其中最根本的原因在于软件的不断演进。它永远不会真正完成,也永远不会被弃用。一旦一行代码被提交,它很可能在未来十年内都无法被移除。问题在于,你没有考虑到那个庞大的内存池会如何影响它,因为没人告诉过你内存池的存在……甚至没人考虑过它。事实上,那一行代码可能会启发其他人提出:既然我们能做到这一点……我们也能构建一个内存池,对吧?我们创造的东西往往会成为下一个事物的灵感来源,甚至可能将其引向一个全新的方向。但是,这个基础足够牢固吗?

还有我们一直沿用的各种遗留问题。如何确保它们能继续正常运行?因为即使我们想在上面添加新的、闪亮的功能,也不能丢掉那些旧的(曾经闪亮的)功能。以前写代码更像是盖房子,我们先写一遍,一年后再写一个改进版,有充足的时间来验证旧的功能……现在可不一样了。

弹力

虽然从架构角度来看,软件的灵活性与流动性(它如何适应变化?)相关,但软件的韧性也与其运行状况息息相关。我们期望软件始终运行,但却在尽可能狭窄的条件下进行开发……“它在我的机器上运行正常”。这在一定程度上解释了为什么开发人员总是说它应该能运行。软件的复杂性在于,将完全相同的代码放到另一台机器上通常会彻底失败!

那么,我们究竟是如何创造出如此复杂的东西,以至于我们甚至无法预测它是否有效,更别提效果如何了?这着实令人印象深刻,因为我们所做的一切最终都只是非此即彼的选择。但为什么我们不能兑现“无论条件如何,它都能始终有效”的期望呢?

我们需要确保所有先决条件都完全满足,否则根本无法运行。像Docker这样的工具可以帮助我们做到这一点,但仍然存在无数我们无法控制甚至无法考虑的变量。混沌工程是提高系统弹性的一种方法,但这需要相当大的投入。此外,它还假设你的软件能够运行,但这究竟意味着什么?仅仅是指在特定条件下,它能在你的机器/生产集群上运行吗?如果我想在一个新的测试集群上运行它呢?在开始进行大规模测试之前,我需要做多少准备工作?

规模

如果你像引言中的AUR构建示例那样,写一个简单的 Bash 脚本,那么你可以忽略所有最佳实践。或者至少可以相对安全地假设它不会被扩展或被其他人使用。但是,风险越大,这些因素就越重要。假设你想提供一个 AUR 构建SaaS平台,那么顶部的那段代码很可能会出现在你的平台上……但你可能还需要考虑微服务架构……以及其他两万个需要考虑的因素。

代码的“质量”取决于你的最终目标。而残酷的现实是,我们很多时候都是在开发过程中才逐渐确定最终目标。那么,我们该如何正确判断代码的可靠性呢?尤其是在产品负责人(PO)催促你尽快发布功能,然后两周后又抱怨下一个功能开发时间太长,因为你需要清理第一个功能留下的烂摊子时。那么,我们该如何编写足够好的代码,避免两周后不得不重写或弃用呢?或者我们应该编写一次性代码?如果是这样,是否存在一种架构能够真正支持这种做法?业务部门会接受吗?毕竟,这是我们的知识产权,对吧?

安全

另一个重要(且被严重低估)的原因是,我们需要如此繁琐复杂的系统,那就是安全。我们在门上安装锁,防止未经授权的人员进入;此外,我们还制作安全的公文包,用于传递重要信息……同时还要想办法应对撬锁者,并持续监控我们的建筑是否符合当今(字面意义上的)标准。或者至少我们应该这样做。

那么,如何编写安全的代码?如何确保依赖项的安全(还记得log4j吗?)?简而言之:你无法做到完全安全……你最多只能编写出破解难度大于破解后所获价值的软件。换句话说,你的软件保护的“金库”越大,所需的安全性就越高。

但很多时候,人们甚至不知道自己写的代码漏洞百出。这也不能怪他们,毕竟这是一个如此庞大而复杂的话题,除了其他各种杂七杂八的知识之外,要完全理解几乎是不可能的。那么,我们该如何编写安全的代码呢?难道要在不了解所有最佳实践或部署代码扫描器(以及理解和处理扫描结果)的情况下吗?或者我们应该从规模的角度来考虑?也就是说,一开始安全漏洞不多,所以谁会在意呢?但如果代码能够默认安全,就像默认具有弹性一样,那该有多好啊……

结论

那就先到此为止吧。通常这里会总结一些经验教训,但这次没什么特别的……除了编程过于复杂……不过我们一开始就知道这一点。

以下是一些我认为我们应该思考的问题……也是我未来文章中可能会探讨的问题:

  • 我们如何才能对我们所建造的一切负起全部责任
    • 换句话说,我们如何才能掌控我们所建造的东西?
  • 我们如何应对软件 不断变化的特性?
    • 我们如何才能掌控我们已经取得的成就以及未来即将发生的事情?
  • 我们是不是直接开始建设,然后根据需要 进行扩展?
    • 或者我们能否轻松地进行规模化建设?
  • 如何才能开发出足够安全的软件?
    • 或者我们能否默认使其 100% 安全?
  • 我们应该为过去70多年来制造的大量垃圾感到自豪吗?
    • 我们如何才能将所有这些知识浓缩到一个人的头脑中,并期望他能创造奇迹呢?

最后,如果我们面对的是一片空白的土地呢?我们该如何应对这些问题?我们能否采取一些截然不同的方法?

文章来源:https://dev.to/matthijsschuurman/why-is-coding-so-ridiculously-overcomplicated-489h