软件开发中最难的是什么?- Denilson N.


软件开发中最难的是什么?
命名,但又不是你想的那样。

我们的知识代表了现实的一小部分。名称是这种知识的可见部分。

Phil Karlton 曾经说过,“计算机科学中只有两件难事:缓存失效和命名事物。”
一个是真正的问题;另一个是定义现实的问题。
行为不当的缓存算法会损害系统的完整性。不恰当的名称会危及系统的整体存在。

我们的知识来自对我们周围现实的体验和解释。通过软件,我们构建了虚拟世界。我指的不是元宇宙意义上的虚拟世界。我的意思是我们脑海中的虚拟世界。

我们建立新世界是因为现实充满了一切。当在虚拟世界中呈现真实世界的体验时,构建过程会经历三个不同的阶段:

  1. 按原样复制体验或活动。
  2. 识别并删除对体验无益的无关步骤和任务
  3. 添加物理世界中没有的有用功能。

以网上购物为例。
这种在线体验融合了现实的许多方面,例如浏览商品和为它们付款。其他概念,例如开车去商店,不会增加任何价值,也不包含在虚拟体验中。还有一些概念只在虚拟世界中有用——而且可能——比如存储信用卡号以备将来购买。

当我们构建这些新现实时,我们必须首先发明事物,然后为这些事物命名。对现实的不明确、非常规或不充分的认识会导致名称混淆。
令人困惑的名称使得难以分享这些名称下的知识。如果没有足够多的人对现实有相同的解释,系统总是会失败。
这个话题在技术性之前是哲学性的话题。

现实的语义
在理解为什么命名困难(且至关重要)之前,我们必须了解我们的大脑如何将我们周围的世界“翻译”成知识。

不仅仅是符号和声音的序列,名称代表了心理学家称之为图式schema的概念。

模式对于软件开发来说可能有点抽象,所以我更喜欢使用一种特殊形式的模式,称为语义网
语义网的最简单形式包含实体和关系:

  • 实体代表不同的工件类型,无论是实际的还是概念上的。如果我们谈论树木,一些可识别的实体将是“树干”、“树枝”、“树叶”、“果实”、“花朵”和“种子”。
  • 关系描述了两个实体之间的关联。例如,叶子“连接”到树枝,果实“长出”树。


语义网络(左侧)识别和连接实体。随着我们对一个主题的了解越来越多,语义网络变得越来越密集,新实体不断添加到网络中。当我们对某个主题了解更多时,我们的大脑也会重组这些网络。

虽然语义网络代表世界,但存在不同但同样有效的表示。有些表示在深度上不同(更详细地看世界),而另一些表示在视角上不同(从不同角度看世界)。

让我们从一个教别人水果起源的例子开始:

  • 蹒跚学步的孩子询问水果的来源可能会对“树”、“树枝”、“花”和“水果”的简单语义网络感到满意。
  • 在以后的生活中,也许作为一名生物学学生,同一个学生将准备好理解更复杂的模型。该模型将包含新的实体,例如“种子”、“花粉”、“花粉管”、“卵巢”和“花粉囊”。这些新实体将需要更多的语义网络关系,连接现有实体和新实体。例如,花粉“存在于”“花粉囊”中,而花粉囊是花的“一部分”。

这两个语义网络在深度上有所不同。

Apilogists——蜜蜂专家——检查树木在他们的语义网络中肯定有许多这样的概念。然而,语义网络可能包括不太常见的实体,例如与蜜蜂在生态系统中的生计和一般生存能力有关的“森林缓冲区”和“防风林”。这些语义网络仍然包含有关树木的额外信息,但仅当该信息与蜜蜂有关时。
那就是视角的不同。

对一门学科的终身研究使不同的人对同一事物的看法不同。根据观察者的专业知识,一个简单的水果可能会唤起越来越复杂和不相关的概念。
这些差异在处理现实时引入了两个基本挑战,您可能会在软件开发中开始认识到这些挑战:

  1. 不同的技能水平意味着不同深度的语义网络。有些人比其他人能在客户场景中看到更多细微差别。这并不意味着额外的感知总是会对最终解决方案产生实质性影响,但它确实存在。
  2. 不同的优先级会影响我们对相同感官信息的分类方式。软件开发人员可能会在服务器、连接和呈现引擎的复杂物理和逻辑网络中识别支撑整个系统的实体。对于用户来说,也许他们看到的只是“一个应用程序”,这是他们日常生活中的一个小实体。

我们或多或少熟悉深度上的差异。我们不太适应观点的差异。


一切都是新的虚拟世界
在这一点上,你可以看出,困难的部分并不是为某一事物命名的最后行为。

一个好名字的背后是对现实的清晰感知和语义网络的形成:概念和关系必须从观察中浮现出来,然后我们才能命名它们。

在有记载的历史的某个时刻,一定有人看着地上的一根高大的棍子,并称它为树。
但是当某人的现实不遵守空间、物质和时间的规则时会发生什么?
这就是软件难题,强调“软”(虚拟)而不是“硬”(具体)。软件开发人员不会简单地为他们世界中的实体指定名称。
通常,他们创建这些实体。


软件开发人员可能会以或多或少明显的方式向另一个观察者解释现实:一些概念可能很简单,并且可以在不同的开发人员之间共享。其他概念可能需要复杂的解释并在团队中形成新的语义网络,然后人们才能围绕它进行协作。

因此,软件开发人员比其他行业的人更频繁地面临命名决策。

举几个其他行业如何遇到命名新事物挑战的例子,我们必须看看他们遇到新事物的频率。

  • 物理学家需要使用能够窥视亚原子粒子深处的仪器来剖析宇宙的基础。
  • 天文学家利用有限的资源四处张望,希望发现一个需要重新命名的天体
  • 企业家必须先创建公司和新产品,然后才能命名。

主题很明确:大量的工作、漫长的发现期、现实世界的限制,也许他们各自领域的顶尖人物每年(或十年)都会获得一些命名机会。

软件开发人员?他们一天有几十次,甚至可能几百次这样的机会。

机会来自于处理一个可以代表程序中的思想和概念的媒介。这些代表必须是严格的、明确的、可被其他程序引用的。


知识转移,从理论到实践
一旦所有新事物都被命名,就该向开发团队的其他成员介绍这些新世界了。
由于大多数软件开发人员缺乏正式的教育背景,他们通常依赖于向他人学习和教授他人的经验。

虽然正式的教学培训可能还很遥远,但我们至少可以略读学习理论的某些方面。
对学习理论的更深入分析值得单独发表。尽管如此,即使是快速阅读“学习迁移”等概念,也显示出解释和改进传统软件设计共享方法的潜力。
值得注意的是,这些传统方法仍应是任何知识转移的基础:


请注意,白板会议是对设计文档的补充,而不是替代。

需要明确的是,现场会议是一种很好的认知工具。“学生”与“教师”互动以探究概念并逐渐增强他们的语义网络。

另一方面,培训的实施在很大程度上取决于教师的经验。个人交付导致跨会话的结果不一致。个性化交付的扩展性也很差,并且边缘化了社交网络、地域和时区边界之外的同事。


隐喻:当心“现成的”语义网络。
不同的人以不同的方式形成语义网络。在软件开发中,当您处理适合多个可能类别的抽象概念时尤其如此。

设计人员和开发人员时不时地喜欢使用隐喻来建模或描述他们的系统也就不足为奇了。他们的想法是,隐喻可以通过建立在人们之前已经学习过的类似语义网络上来加快知识转移。

隐喻是新语义网络的固定版本。接收者可以“从头开始”构建一个新的语义网络,但可以从类似语义网络的预先存在的片段中构建得更快一些,如果不是更完美的话。

一个简单比喻的例子是使用“垃圾桶”图标从计算机中删除文件。隐喻的一个更详细的例子是消息系统中“队列”概念的增选。

但是,使用隐喻有(至少)两个问题:

隐喻问题 #1:什么才是为人所众知?
第一个问题是,熟悉的概念不一定具有普遍性。

一个完美的例子来自名为“ Darmok ”的星际迷航剧集:
这个例子很完美,因为《星际迷航》的粉丝们已经明白我的意思了,其他的就不用多说了。与此同时,非粉丝们想知道为什么这一集有意义。
在那一集中,一个外星种族通过根植于他们家乡历史事件的隐喻,在一个星球范围内的内幕故事中进行交流。结果,与任何其他社会的交流都因缺乏共享背景而受到阻碍,导致长期而无果而终的接触尝试。
附带一提,这个故事还展示了共享上下文的不可思议的力量,其中最少的语言提示可以快速传达密集的信息。
索卡斯,他的眼睛没有遮盖!

隐喻问题 #2:深度和视角。
我们再一次面对我们的语义网络恶棍。

如果我说某个东西“像互联网一样工作”,技术人员可能或多或少地了解互联网。同时,一些没有云基础架构背景的互联网高手可能会把它看成是子。

即使在云基础设施工程师的技术社区中,有些人可能在 2021 年 Facebook 大规模中断之前从未听说过BGP(边界网关协议)。像我这样的人,当时刚从阅读文章中了解 BGP,可能已经将 BGP 添加到他们的语义网络中,作为[将大公司连接到公共互联网的协议]的一些松散版本。
学习理论也有证据表明,在不同经历中传递知识并不是一项通用技能:

  • 学习的转移是指一个人在学校学到的东西以某种方式延续到与那个特定时间和特定环境不同的情况中......他发现,尽管转移对学习极为重要,但这是一个很少发生的现象。
  • 事实上,他进行了一项实验,让受试者估计特定形状的大小,然后他会切换形状。他发现先验信息对受试者没有帮助;相反,它阻碍了他们的学习。 


抽象就一直是抽象。
将抽象视为将整个语义网络分组为新语义网络中更少的实体。
抽象可以实现更高层次的通信,无论是人与人之间还是人与机器之间。

作为现实世界抽象的一个例子,想想在线购物,其中购买体验在计算机屏幕上用图像和按钮表示。

观察现实的系统设计者必须就现实的哪些部分在新现实(新系统)中必不可少做出明智的决定。那个新现实是对原始现实的抽象。

虽然抽象并不是软件所独有的,但软件行业的独特之处在于它以多快的速度将抽象堆叠在抽象之上
这种堆叠会产生难以理解的语义网络层,需要陡峭的学习曲线。

为了说明这些软件抽象堆栈有多“高”,让我们尝试向外行解释Kubernetes 集群:
Kubernetes集群将各种 Kubernetes节点组合成一个逻辑计算单元,其中包含这些节点的聚合计算能力。Kubernetes 节点是计算能力的抽象,它可以是运行在虚拟机管理程序上的虚拟机、实际的裸机服务器,甚至是桌面上的一小块Raspberry Pis 。
您可以在 Kubernetes 节点上运行程序。但首先,您需要了解“ pods ”的抽象概念,即部署在一起的“容器”组。

解释可以继续,直到它不可避免地压倒了一个人扩展其语义网络的能力。

这里的要点是,更简单并不意味着简单。有些技术本身就很复杂,因为它们解决的是复杂的问题。

因为我们需要就这些问题的解决方案进行有效推理和沟通,所以我们不断在抽象之上分层抽象,直到解释看起来很像现在著名的“乌龟叠罗汉”的故事:
"乌龟跌罗汉 "是对无限退步问题的一种表达。这句话暗指神话中的世界乌龟,它的背上支撑着一个平坦的地球。它表明这只乌龟是在一只更大的乌龟的背上,而这只乌龟本身也是一列越来越大的乌龟的一部分,而且是无限地延续下去。

软件开发人员,或者至少是软件架构师,最好略读一下认知负荷的概念。

从无处不在到独一无二
随着我们的学习,语义网络的数量和规模都在增长。它们也像树一样生长,新概念附加到现有概念上。我们可以在大脑中重组部分语义树,但完全“重写”并不容易或不常见。

两个人的语义网络可能会更靠近他们的核心,尤其是当这些人共享很多上下文时。

随着抽象级别的提高,共享上下文减弱,并且这些语义网络在边缘变得越来越不同。当这些网络变得足够不同时,这些人就不再有相似的观点。

作为软件开发中的热身练习,让我们看一下在函数内命名变量的狭窄范围。函数内的语义网络在用途上非常有限,程序员试图使名称适合局部控制循环或算法的一部分。
循环控件需要命名索引变量或枚举元素。这些命名决定在很大程度上是衍生的和无关紧要的。例如,当涉及到索引变量时,默认决策通常遵循单字符名称的历史模式,如“ i ”、“ j ”和“ k ”。

两个不同的开发人员接受过类似的培训,面对一个简单任务的相同抽象定义,可能会得出看起来非常相似的解决方案。
但是当相同的开发人员处理更高级别的抽象时,这种默契相似的协议可能会消失。

通过循环控制,我们正在处理具有更高语义目的的命名实体。在这一点上,我们可能试图使名称符合包含变量的函数的目的。

随着命名工作在概念层次上的上升,我们开始将名称与系统概念相统一。例如,如果一个系统有一个代表购物车的组件,我们可能会有一个文件或一个系统类以类似于购物车的方式命名。

这里重要的是,不同的人为同一个现实创造不同的语义网络。当这个现实非常狭窄时,比如说 "循环遍历这个数组",语义网络同样是有限的,并且倾向于完全一致。
随着范围的逐渐扩大(控制流、将算法建模为程序,或者定义一个系统,),是否能对齐一致的程度与范围大小有直接关系。

当我们考虑到与客户如何看待系统心理模型中的”一致性“时,一致性这个词无法开始涵盖这个差距,对客户来说,构成我们世界和专业知识中心的东西可能仅仅是 "供应商6号的3号应用"。

回到我们的购物车的例子,两个不同的程序员在将购物车功能表现为源代码文件方面会做出相同的决定吗?可能不会。
更重要的是,他们会同意别人的决定并调整他们的心理模型吗?有可能。

达成一致意味着在团队中不同的人之间 "协调 "语义网络,而且有一些很好的技术可以做到。前面提到了一些正式的方法(设计文档、术语词典、组件图、顺序图。)
我想加上频繁的交叉教育,甚至结对编程,作为可以补充正式文档的活动。对提高整个开发团队的 "语义一致 "的方法的详细分析,是另一个专门发帖的主题。
不同的语义网络的话题把我们带到下一节。


通过代码重写现实
重做某人的工作是软件行业中独特的常见做法。

乍一看,返工是一种浪费的活动。从表面上看,也许从项目经理的角度来看,如果一段代码正在完成它的工作,开发人员应该不要管它。如果代码块需要最少的修改来添加新功能,那么用增量来扩充该组件并保持其余部分不变应该更有意义。正确的?

这里的脱节是,项目经理们正在考虑建造东西。本能地或通过培训,项目经理期望当你在现实世界中建造东西时,它就完成了。

然而,软件开发者不是在现实世界中建造东西,他们是在解释现实,创造一个虚拟世界。

撇开代码重构的正当理由不谈,软件开发人员从另一个开发人员“继承”代码库可能仍然会感到必须重写该代码库重要部分的冲动。

这是为什么?
一句话:建构主义

再多说几句,一个系统中预先存在的代码不是一个静态的人工制品。它是对现实的一种解释。在一个虚拟世界中,制造的是一个不断变化的现实。在这些条件下,在整个团队中对现实达成一个共同的、稳定的理解几乎是不可能的。

重写代码并不是在重建代码库中已有的东西的无意义的练习。它是一项在开发人员所知道的和书面表达的知识之间建立平衡的工作。
重写好处:

  • 这种平衡使开发人员能够更有成效地使用代码库。
  • 重写的代码库是学习活动的一个副产品。


如果源代码与新开发人员希望对代码应该做什么的想法不符时,许多开发人员可能会发现重写代码比重建他们的思维结构和对要解决的问题的推理更容易。

请注意,我明确解决了代码库从一个开发人员转移到另一个开发人员的场景。

在完成代码重写活动并形成新的语义网络来表示他们对世界的解释之后,这位试图重写的开发人员为新概念选择旧名称的几率相对较低。

自我可能起到了一定的作用——命名事物就像在世界上留下一个小印记——但至少有一个非常务实的原因:一个带有旧名称的新概念必然会让熟悉旧概念的人感到困惑——见这个关于传播激活主题的优秀会议供参考。

新的现实会产生新的名字


结论
命名(东西)是创造抽象世界的复杂过程的高潮。

这些世界的某些部分是物理世界的直接代表,而其他部分则是全新的。

命名过程包括狭窄的任务(写一个简短的控制循环)和广泛的认知练习(创造新的抽象概念)。

由于缺乏历史参考资料--与其他专业领域相比,软件开发是一门相对较新的学科--需要经常部署不完善的隐喻来解释新的范式。

前所未有的技术进步要求不断增加的抽象层,将现代系统令人匪夷所思的能力分层化。

这些挑战中有许多是我们这个行业的年轻所固有的,但我们可以从其他领域借用各种概念来帮助我们解释、协调和解决这些问题。