如何管理软件项目中的意外复杂性


74

当问到Murray Gell-Mann时,Richard Feynman如何解决了这么多难题时,Gell-Mann回答说Feynman有一个算法:

  1. 写下问题。
  2. 认真思考。
  3. 写下解决方案。

盖尔曼(Gell-Mann)试图解释说,费曼是另一种类型的问题解决者,研究他的方法没有任何见识。对于管理中型/大型软件项目的复杂性,我有点相同的感觉。优秀的人天生就是擅长的,并且设法以某种方式分层和堆叠各种抽象,以使整个事物易于管理,而不会引起任何不必要的麻烦。

那么,费曼算法是管理意外复杂性的唯一方法,还是软件工程师可以始终如一地应用实际方法来应对意外复杂性?


32
如果写下问题(充实问题,以便您可以充分地向其他人解释)的行为帮助您确定了解决方案,我不会感到惊讶。
罗里·亨特

@RoryHunter-同意。写下问题并与某人分享的一部分表示您承认自己还没有解决方案。
JeffO 2014年

38
@RoryHunter:这个。几乎每周,我都会遇到一个我无法解决的问题,请给某人写一封电子邮件进行解释。然后意识到我没有考虑的是什么,解决问题并删除电子邮件。我还在该网站上写了十几个从未收到过的问题。
pdr 2014年

我了解到的最基本的事情是不要要求开发人员处理他们可以解决的问题。这些问题令人兴奋,但同时也涉及开发人员扩展到陌生的地方,从而导致大量“即时”增长。诸如螺旋开发之类的某些工具在将其逐步发展为最终解决方案之前,可以使开发团队在一个较小的棘手问题
Cort Ammon 2014年

2
@CortAmmon并不是故意的,但这听起来像是一个愚蠢的见解。开发人员知道的99%的信息是通过您所谓的“即时增长”而获得的。一个好的问题解决者需要一个好的程序员。解决问题是我们固有的吸引力。如果您的开发人员没有成长,他们可能正在做很多无聊的重复性工作。会使任何有才华的开发人员感到不高兴和沮丧的工作类型。而且...“螺旋式开发”不过是对瀑布式里程碑式迭代开发的基本概念的重新混编。
Evan Plaice

Answers:


104

当您看到一个好的举动时,寻找一个更好的举动。
—伊曼纽尔·拉斯克(Emanuel Lasker),已27年的世界象棋冠军

以我的经验,偶然复杂性的最大驱动力是程序员坚持初稿,只是因为初稿确实起作用。这是我们可以从英语作文课中学到的东西。他们及时安排了作业,并结合了老师的反馈来完成一些草稿。出于某些原因,不对类进行编程。

有许多书籍以具体,客观的方式来识别,表达和修复次优代码: 清洁代码有效使用旧版代码,以及许多其他方法。许多程序员都熟悉这些技术,但并不总是花时间来应用它们。他们完全有能力减少意外的复杂性,只是没有养成尝试的习惯。

问题的一部分是,除非在早期阶段就经过同行评审,否则我们很少会看到其他人的代码的中间复杂性。干净的代码看起来很容易编写,而实际上通常涉及多个草稿。首先,您要写出最好的方法,注意它引入的不必要的复杂性,然后“寻找更好的动作”并重构以消除这些复杂性。然后,您继续“寻找更好的举动”,直到找不到一个。

但是,直到所有搅动之后,您才将代码发布以供审查,因此从外部看,它似乎也像费恩曼一样。您倾向于认为您无法像这样全部完成全部工作,因此您不必费心尝试,但事实是,您刚刚阅读的漂亮简单代码的作者通常无法将全部内容全部写成一个块要么这样做,要么如果可以的话,那是因为他们以前曾经多次编写过类似的代码,并且现在无需中间阶段就可以看到模式。无论哪种方式,您都无法避免吃水。


1
嗯,但是您似乎可以在第一次尝试时为这个问题写一个干净的答案。(还有一个非常有说服力的人。)也许您只是伪装成Feynman。
kmote

1
tl; dr; 重构,不要害怕。
ocodo 2014年

1
+1完美无瑕。伙计,这是每个人都谈论的话题,但很少有人这样做。我试图重新思考自己,就像机器学习算法一样思考自己,那里的错误实际上很不错,并教授如何改进。用“草稿”隐喻表达短语的可爱方式。
Juan Carlos Coto 2015年

46

“不能教授软件体系结构技能”是一个普遍的谬论。

很容易理解为什么许多人会相信它(擅长此事的人想相信自己在神秘上很特别,而那些不想相信的人并不是他们的错,这不是他们的错。)然而是错误的;该技能只是比其他软件技能(例如,理解循环,处理指针等)更加注重实践。

我坚信,构建大型系统很容易受到反复练习和经验学习,就像成为一名出色的音乐家或演说家一样:最少的才能是先决条件,但这并不是令人沮丧的最低要求大多数从业者的影响力。

处理复杂性是您通过多次尝试和失败而获得的一项技能。仅仅是社区发现了许多关于大型编程的通用准则(使用层,在抬头的任何地方都进行复制,虔诚地坚持0/1 /无限...)对于一个社区来说显然不是正确的和必要的。直到他们真正开始编写大型程序为止。直到仅仅几个月后,您就被重复复制所困扰,而重复复制才引起问题,您根本无法“理解”这些原则的重要性。


11
我真的很喜欢你的最后一句话。我很肯定我在第一份工作中学到了很多东西,因为我在那里工作的时间足够长,我的错误会赶上我。这是宝贵的经验。
MetaFight 2014年

26
“好的判断来自经验。经验来自坏的判断。”
穆拉·纳斯鲁丁(

9
我不明白您如何断言不能教授软件体系结构是一个谬论,但是要继续说,反复练习,从经验中学习和犯错误(再加上一些天生的才干)是学习它的唯一方法。 。我对可以教的东西的定义是,您无需费力地练习就能获得,但可以通过观看,聆听和阅读来学习。我同意这一答案中除第一行以外的所有内容,因为它显然与其余内容矛盾。
托马斯·欧文斯

4
关键是很多人声称,无论在任何情况下,无论是在演讲室还是在行业中,很多人绝对无法学会成为优秀的建筑师,因为他们“没有得到所需的东西”。我认为这是普遍但错误的。
Kilian Foth,2014年

5
@托马斯:回到公众演讲的比喻。不管您阅读了多少书,花了多少时间研究该主题,还是有多少老师试图教您精通公开演讲,除非您通过反复练习,从经验中学习,否则您将无法做到这一点。和犯错误。您永远不会说服我,有人可以通过观看,收听和阅读来学习该技能。大多数技能,包括软件体系结构,都一样。实际上,我想不出只要看,听,读就能学到的任何技能。
Dunk 2014年

22

安迪·亨特的务实思想解决了这个问题。它指的是Dreyfus模型,据此模型可以精通各种技能的5个阶段。新手(第1阶段)需要准确的说明才能正确执行操作。相反,专家(第5阶段)可以将一般模式应用于给定问题。引用这本书,

专家通常很难对他们的行为进行详尽的解释。他们的许多回应都被很好地实践,以至于成为前知之举。他们的丰富经验被大脑的非言语,前意识区域挖掘,这使我们难以观察,也难以表达。

当专家做他们的事情时,对我们其他人来说似乎几乎是神奇的-奇怪的咒语,似乎无所不在的洞察力,以及当我们其他人甚至不确定所有事情时,似乎拥有不可思议的能力来知道正确答案的能力。关于这个问题。当然,这不是魔术,而是专家对世界的看法,解决问题的方式,使用的心理模型等,与非专家明显不同。

看到(并因此避免)不同问题的一般规则可以专门应用于意外复杂性问题。拥有一组给定的规则不足以避免此问题。总是存在那些规则未涵盖的情况。我们需要积累经验,以便能够预见问题或确定解决方案。经验是无法教授的,只有不断地尝试,失败或成功并从错误中学习才能获得经验。

来自Workplace的这个问题是相关的,在这种情况下,恕我直言将很有趣。


3
关于专家的想法的精彩描述。确实不是魔术,只是很难阐明所有离散和逻辑的步骤。
MetaFight

+1非常类似于“能力
Robbie Dee 2014年

这就像在说“精英”运动员如何做自己的事情并不是魔术,这只是他们能够自然地执行最高水平的必要的离散和逻辑步骤的问题。因此,如果只有那些运动员可以向我们阐明那些离散且合乎逻辑的步骤,那么我们所有人都可以成为精英运动员。无论我们获得何种知识水平,我们所有人都可以成为精英运动员的概念,与我们所有人都可以成为专家一样,无论我们要学习什么技能,无论该技能领域的能力如何,这都是荒谬的。
Dunk 2014年

1
@Dunk,魔术将是那些“精英”运动员无需进行任何培训即可表现相同的东西。主要思想是没有灵丹妙药。无论多么有才华,仅仅通过研究一些“离散而合乎逻辑的步骤”都无法获得经验。顺便说一句,根据同一本书,只有1-5%的人是专家。
superM 2014年

@Super:我会质疑任何这样荒谬的说法的书/研究,因为只有1-5%的人是专家。谈论从他们的#&#&$#中取出一个数字。专家在做什么?我敢打赌,在呼吸,行走和进食方面,熟练的人比例要高得多。谁决定什么是专家级别?诸如1-5%的索赔将使此类作者的任何进一步索赔和分析不符。
Dunk 2014年

4

您无需说明,但“意外复杂性”定义为与“基本”复杂性相比,不是问题固有的复杂性。“驯服”所需的技术取决于您从何处开始。以下内容主要是指已经获得不必要的复杂性的系统。

我在许多大型的多年项目中都有经验,“偶然的”部分远远超过了“基本”方面,甚至超过了那些没有的方面。

实际上,费曼算法在一定程度上适用,但这并不意味着“认真思考”仅意味着无法被编码的魔法。

我发现有两种方法需要采用。两者兼而有之-它们不是替代品。一种是零散解决,另一种是进行大量返工。因此,可以肯定地“写下问题”。这可以采取对系统进行审核的形式–代码模块,它们的状态(气味,自动化测试的水平,声称有多少员工了解它),总体体系结构(即使有“问题”,也只有一个)。 ),需求状态等。

“偶然的”复杂性的本质是,没有一个问题需要解决。因此,您需要进行分类。就维护系统和发展系统的能力而言,它在哪里受到伤害?也许某些代码确实很臭,但不是最高优先级,可以进行修复以等待。另一方面,可能会有一些代码可以快速返回重构所花费的时间。

为更好的体系结构定义一个计划,并尝试确保新工作符合该计划–这是一种增量方法。

此外,请阐明问题的成本,并以此来构建业务案例以证明重构的合理性。这里的关键是,架构良好的系统可能更健壮和可测试,从而可以缩短实施变更的时间(成本和进度)–这是真正的价值。

重大的返工确实属于“认真思考”类别–您需要正确进行。在这里,拥有一个“费曼”(Feynman)(很好,只有一小部分就可以了)确实可以带来巨大的回报。不会导致更好的体系结构的重大返工可能是一场灾难。完整的系统重写为此而臭名昭著。

任何方法的隐含含义都是知道如何区分“偶然的”和“必要的” –也就是说,您需要有一个真正了解系统及其目的的优秀架构师(或架构师团队)。

说了这么多,对我来说关键的是自动化测试。如果拥有足够的资源,则系统处于受控状态。如果你不这样做。。。


您能否解释一下自动化测试如何区分意外和本质复杂性?
ryscl 2014年

1
@RyanSmith。简而言之,不是。实际上,我认为没有什么特别的方法(除了“认真思考”之外)可以区分这些。但是问题是关于“管理”它。如果将需求,设计,测试用例视为系统体系结构的一部分,那么缺乏自动化测试本身就是偶然的复杂性,因此在缺乏自动化测试的地方添加自动化测试确实可以解决问题,并使之更易于管理。但最肯定的是,它并不能解决所有问题。
基思

3

应该使一切都尽可能简单,但不要简单。
–归因于爱因斯坦

让我勾勒出我的个人算法来处理意外的复杂性。

  1. 编写用户案例或用例。与产品负责人一起审查。
  2. 编写一个集成测试,由于该功能不存在而失败。如果您的团队中有此类事情,请与质量检查人员或首席工程师进行审查。
  3. 为一些可以通过集成测试的类编写单元测试。
  4. 为那些通过单元测试的类编写最小的实现。
  5. 与其他开发人员一起审查单元测试和实施。转到步骤3。

整个设计的魔力在于步骤3:如何设置这些类?这变成了一个相同的问题:您如何想象自己有解决方案,然后才有解决方案?

引人注目的是,想象自己有解决方案似乎是写解决问题的人的主要建议之一(Abelson和Sussman在“计算机程序的结构和解释 ”中称为“如意算盘”,在Polya的《如何解决

另一方面,并​​非每个人都有相同的“ 想象中的解决方案的品味 ”:有些解决方案只有您觉得优雅,而其他解决方案则为更广泛的读者所理解。这就是为什么您需要与其他开发人员一起对代码进行同行审阅:并不是为了调整性能而是要就可以理解的解决方案达成共识。通常,这会导致重新设计,并且在进行一些迭代之后会导致更好的代码。

如果您坚持编写最少的实现以通过您的测试,并且编写许多人都理解的测试,那么您应该以仅保留不可简化的复杂性的代码库结尾。


2

偶然的复杂性

最初的问题(解释为)是:

架构师如何管理软件项目中的意外复杂性?

当那些负责项目指导的人选择添加一次性的技术时,偶然的复杂性就会出现,并且该项目的原始架构师的总体策略并不打算引入该项目。因此,在策略中记录选择背后的原因很重要。

坚持原来策略的领导层可以避免意外的复杂性,直到明显有必要背离该策略的时候。

避免不必要的复杂性

根据问题的内容,我将其改写为:

架构师如何管理软件项目的复杂性?

这种表述更贴切于问题的主体,然后在其中引入费曼算法,从而提供了一个上下文,该上下文提出,对于最佳建筑师,当遇到问题时,他们应具有格式塔,然后从中巧妙地构造解决方案,并且我们其他人不能指望这一点。有一定的理解力取决于主题的才智,以及他们学习可能在其范围内的体系结构选项功能的意愿。

项目规划过程将利用组织的学习来列出项目需求,然后尝试构建所有可能选项的列表,然后将选项与需求进行协调。专家的格式塔使他可以快速地执行此操作,并且可能几乎不需要做任何明显的工作,这使他看起来很容易做到。

我向你表示,这是因为他的准备。拥有专家的格式像需要熟悉您的所有选择,并且具有远见卓识,以提供一个简单的解决方案,可以满足确定项目应提供的可预见的未来需求,并具有适应不断变化的需求的灵活性。该项目。费曼的准备工作是他对理论和应用数学以及物理学的各种方法都有深刻的理解。他天生好奇,聪明到足以理解他对周围自然世界的发现。

这位专家级的技术架构师将具有类似的好奇心,这将基于他们对基本原理的深刻理解以及对多种技术的广泛了解。他(或她)将有智慧地借鉴跨领域成功的策略(例如Unix编程原理)和适用于特定领域的策略(例如设计模式样式指南)。他可能不十分了解每种资源,但是他会知道在哪里可以找到该资源。

建立解决方案

可以从经验和教育中汲取这种知识,理解和智慧的水平,但是需要智力和智力活动来组合完形战略策略解决方案,该解决方案可以避免意外和不必要的复杂性而协同工作。它要求专家将这些基础知识放在一起。这些是德鲁克首次提出该术语时所预见的知识工作者。

返回最后的具体问题:

可以从以下几种来源中找到解决意外复杂性的特定方法。

遵循Unix编程原理,您可以创建简单的模块化程序,这些程序可以很好地工作并具有通用接口。遵循设计模式将帮助您构建复杂的算法,这些算法不会比必要的复杂。遵循样式指南将确保您的代码可读,可维护,并且对于编写代码所用的语言而言是最佳的。专家们将把这些资源中发现的许多原理内在化,并能够以一种紧密结合的无缝方式将它们组合在一起。


您所说的“格式塔”是什么意思?我发现它很像“范式”,通常被滥用,或用来给学术界一种气氛。

@JonofAllTrades来自维基百科:`格式塔(Die Gestalt)是形或形的德语单词。它用英语指代“整体性”的概念。我在这里用它来指的是专家对整个图片的理解,即人眼
Aaron Hall

0

几年前这可能是个难题,但如今消除国际海事组织的偶然性不再是困难。

肯特·贝克在某个时候对自己说:“我不是一个伟大的程序员;我只是一个有良好习惯的优秀程序员。”

IMO有两点值得强调:他认为自己是程序员,而不是建筑师,他的重点是习惯,而不是知识。

费曼解决难题的方法是唯一的方法。描述不一定很容易理解,因此我将对其进行剖析。费曼的脑袋不仅充满知识,还充满运用这些知识的技巧。当您拥有使用知识和技能的同时,解决难题就既不容易也不容易。这是唯一可能的结果。

有一种完全不神奇的方式来编写干净的代码,它不包含偶然的复杂性,并且与费恩曼所做的大部分相似:获得所有必需的知识,进行培训以习惯于将其付诸实践,而不仅仅是将其藏起来在大脑的某个角落,然后编写清晰的代码。

现在,许多程序员甚至都不知道编写简洁代码所需的全部知识。较年轻的程序员倾向于放弃有关算法和数据结构的知识,而大多数较老的程序员往往会忘记它。或大O符号和复杂性分析。较老的程序员倾向于忽略模式或代码气味-甚至不知道它们的存在。任何一代的大多数程序员,即使他们了解模式,也永远不记得确切的使用时间和驱动程序部分。很少有任何一代的程序员会根据SOLID原则不断评估其代码。许多程序员在各处混合了所有可能的抽象级别。我暂时不知道有哪个程序员会根据Fowler在他的重构书中描述的那些痕迹不断评估他的代码。尽管某些项目使用某种度量工具,但最常用的度量是一种或另一种复杂性,而其他两个度量(耦合和内聚)在很大程度上被忽略,即使它们对于干净的代码非常重要。几乎每个人都忽略的另一方面是认知负担。很少有程序员将单元测试视为文档,甚至更少的程序员意识到难以编写或命名单元测试又是另一种代码恶臭,这通常表示分解很糟糕。极少数人知道域驱动设计的口头禅,就是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。最常用的度量标准是一种或另一种复杂度,而其他两个度量标准(耦合和内聚性)即使在干净的代码中非常重要,在很大程度上也会被忽略。几乎每个人都忽略的另一方面是认知负担。很少有程序员将单元测试视为文档,甚至更少的程序员意识到难以编写或命名单元测试又是另一种代码恶臭,这通常表示分解很糟糕。极少数人知道域驱动设计的口头禅,就是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。最常用的度量标准是一种或另一种复杂度,而其他两个度量标准(耦合和内聚性)即使在干净的代码中非常重要,在很大程度上也会被忽略。几乎每个人都忽略的另一方面是认知负担。很少有程序员将单元测试视为文档,甚至更少的程序员意识到难以编写或命名单元测试又是另一种代码恶臭,这通常表示分解很糟糕。极少数人知道域驱动设计的口头禅,就是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。而另外两个指标(耦合和内聚)在很大程度上被忽略,即使它们对于干净的代码非常重要。几乎每个人都忽略的另一方面是认知负担。很少有程序员将单元测试视为文档,甚至更少的程序员意识到难以编写或命名单元测试又是另一种代码恶臭,这通常表示分解很糟糕。极少数人知道域驱动设计的口头禅,就是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。而其他两个指标(耦合和内聚)在很大程度上被忽略,即使它们对于干净的代码非常重要。几乎每个人都忽略的另一方面是认知负担。很少有程序员将单元测试视为文档,甚至更少的程序员意识到难以编写或命名单元测试又是另一种代码恶臭,这通常表示分解很糟糕。极少数人知道域驱动设计的口头禅,就是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。几乎每个人都忽略的另一方面是认知负担。很少有程序员将单元测试视为文档,甚至更少的程序员意识到难以编写或命名单元测试又是另一种代码恶臭,这通常表示分解很糟糕。极少数人知道域驱动设计的口头禅,就是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。几乎每个人都忽略的另一方面是认知负担。很少有程序员将单元测试视为文档,甚至更少的程序员意识到难以编写或命名单元测试又是另一种代码恶臭,这通常表示分解很糟糕。极少数人知道域驱动设计的口头禅,就是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。我们的口号是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。我们的口号是使代码模型和业务域模型尽可能地接近,因为差异必然会在以后造成问题。如果您希望代码干净,则需要始终考虑所有这些因素。还有很多我现在不记得的事情。

您想编写干净的代码吗?不需要魔术。只需学习所有需要的内容,然后使用它来评估代码的清洁度,然后进行重构,直到满意为止。并继续学习-软件仍是一个新兴领域,新见识和知识正在快速获得。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.