The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:
敏捷与否,一个软件项目都是从需求分析和定义开始的。我们基本上会以某种方式定义需要做些什么,不论是在一张餐巾纸上还是一份100页的Word文档上。下一步是将其尽快转化为一个可工作的软件,并尽可能地节省开支。理想情况下,这个原型制作过程需要一周时间,并由一个独立工作的架构师完成。一旦“骨架”准备好了,我们就开始填充软件的“内容”。我们会雇佣一个程序员团队或者外包出去来完成这个任务。在骨架创建阶段,我看到了九个重要的步骤;让我逐一向您展示。
让我们使用一些例子来更具说明性。假设我是一个软件架构师,这个项目是一个“谷歌杀手”。我们被雇佣来创建一个新的搜索引擎,我的工作是将需求转化为一个原型,也就是一个骨架或者概念验证。这是我作为输入的内容(假设是一张餐巾纸…对于一个谷歌杀手来说,还能是什么呢?):
对我来说,这似乎是一个可行的项目,并且需求文档足够清晰。虽然文档没有提到性能,但我可以假设它必须和谷歌一样快。可扩展性、应对压力的能力等也同样如此。
我不打算讨论软件是如何在特定技术栈中创建的。这对本文来说并不重要。现在重要的是我的编程工作将如何“封装”。换句话说,在一周的辛苦工作后,我将把什么交给程序员团队,我的产品是什么,或者更正式地说,我的交付成果是什么。
因此,让我们假设我成功创建了一款软件,并且它能够正常工作。
首先,我必须记录我的关键技术决策及其替代方案。我们通常在GitHub上工作,最好的文档媒体是存储库根目录下的README.md
文件。我只需以纯文本的Markdown格式将我的文本放在那里。这对于一个好的技术文档来说足够了——它必须要简洁;这一点很重要。
对于我做出的每个决策,都必须至少有一个我考虑过但拒绝的替代方案。在我的清单顶部有两个项目:
这些决策是非常高层次的,但我仍然需要记录下来。正如你所看到的,我并没有详细解释为何拒绝了其他选择,这是我的选择。如果将来有人对我的决策提出质疑,他们可能会说这些替代方案没有得到适当的分析。很清楚这是谁的错——我的错。所以我对我所做的这两个选择:Lucene和Java 8 会承担全部责任。
又添加了一个项目到列表中:
然后,我附上一个简单的图表来说明我的决定:
[Lucene] -down- [UI] [Lucene] - [Scraper] [Analyzer] - [Lucene]
如你所见,在这种情况下,我完全忽略了所有的替代方案。我甚至没有提到它们。再次强调,我对此负有全部责任;我说过,“我没有看到任何替代方案。”如果以后发现了更好的替代方案,我们会很明显地意识到为什么我们忽略了它以及是谁的责任。这不仅仅是为了惩罚,而是为了纪律和决策的可追溯性。每个决策必须可以追溯到做出决策的人。这有助于我们避免以后做出糟糕的决策,并使整个项目更易于维护和透明。
让我们再添加一个决策到列表中:
在这种情况下,我记录了其他选择,并说明了它们为何对我们不利。正如你所看到的,这些原因非常偏见;我基本上表达了我对这三个框架的个人观点,并明确偏向于我自己的开源Takes框架。这是好的吗?不,不是。但我是架构师,我会按照我认为对项目有利的方式去做。
我试图表明,这份文档的目的是让我作为架构师解释我的思维方式,无论它有多糟糕、有多么有偏见或者不合理。我必须把我的决定写下来,让项目了解全部情况。
我建议你将记录的决策数量控制在四到十二个之间。如果少于四个,那可能是我忘记记录一些重要内容了。超过12个——我正在记录过多的不重要的决策。对于这种情况,我应该使用其他媒介,如JavaDoc块或响应式类。
README.md
文件中的下一章必须详细解释我是如何解决初始需求中提出的所有问题的。我在上面提到,毋庸置疑,我们的系统必须像谷歌一样快速和可扩展。因此,让我们假设存在两个”问题”—性能和可扩展性。
作为软件架构师,我必须同时解决这两个问题。换句话说,我必须证明我的解决方案既快速又可扩展。也许它并不是,但如果我相信它是,我必须解释为什么我这样认为。我不能对这些问题保持沉默。以下是我对性能的看法:
而这个关于可扩展性的问题:
就像你所看到的,我正在努力保持诚实并说出实情。我们将能够稍后审查这些陈述并决定我是对还是错。但我们需要回答所有在要求中提出的关切。
下一节是关于我在与原型工作时所做的假设。当我们没有足够的事实信息时,我们通常会做出假设,以填补空白。这没有错,但我们必须记录填补了哪些空白以及为什么填补它们。
这两个假设怎么样呢?
我在没有对情况进行适当分析的情况下作出了这些假设。我不知道 Twitter 是否会高兴地看到每小时数百万个请求来自我们的服务器。也许它会封禁我们,我不知道。我不需要评估这个并找到确切的答案。我只是做了一个 假设 并记录下来。
只有 Lucene,没有任何额外的数据持久化层,是否足够?我不知道,但是我 希望 是的。我没有时间对我们整个数据模型及其潜在未来需求进行详细分析。我只是做出一个假设,然后结束了。
如果在交接过程中,项目发起人说这个假设给项目带来了太多风险,我们将进行更好的分析。现在,我的工作是记录我所看到的并继续前进。记住,我只有一周的时间。
现在我列出了我预见到的所有潜在问题,并估计了它们的概率和影响。让我先给你举个例子:
方括号内的第一个数字表示概率,第二个数字表示影响程度,以0到9的标度进行评定。如果两个数字都是九,那就不再是风险,而是事实。如果两个数字都是零,我们可以简单地忽略这个风险。
我只列出了两个,但在一个真实的系统中,应该有四到十二个风险。太多的风险表明原型系统没有足够的专注度,而太少则说明注意力不够。
现在我必须确保产品在持续集成中进行”封装”,这是任何软件包的关键组成部分。我必须进行配置,最好在云中,并确保构建是干净的。
同时,确保持续集成流水线覆盖所有关键领域也非常重要,其中包括:
运行集成测试和单元测试。
静态分析。
收集测试覆盖率。
生成文档。
越严格的流程对项目越有利。在这个阶段,作为一名架构师,我的工作是为产品建立一道“守护墙”,以保护它免受未来的混乱。这种混乱将来自程序员通过拉取请求进行更改。他们对产品的整体质量关注程度远低于我,这就是为什么我必须融入能够控制情况的工具。
我的目标是使持续集成流程尽可能“脆弱”。任何小错误都应该导致构建失败。当然,我所说的是可重现的错误。构建应该以可预测的方式失败,而不是零星地失败。
这是任何软件项目的另一个关键组成部分。您必须对代码的质量进行静态分析。在最原始的方法中,静态分析将检查源代码的格式,并在格式错误时使构建失败。然而,在更高级的变体中,静态分析将捕捉许多重要的错误。
它被称为“静态”,因为它不需要软件运行。相反,单元测试通过运行应用程序来验证软件的质量。
有许多静态分析工具,几乎适用于每种语言和格式。我强烈推荐您使用它们。此外,我建议您尽可能严格地配置它们,以使构建尽可能脆弱。构建的脆弱性是软件开发的关键成功因素。
每次构建都必须收集测试覆盖率,并至少进行报告。在理想情况下,低测试覆盖率必须导致构建失败。假设我将所需覆盖率设置为75%(实际上这是一个更复杂的度量方法,但在简单的方法中只需要一个数字)。如果有人引入一个没有单元测试的新类,则覆盖率百分比会降低,构建将中断。
作为一个创建原型的架构师,我的工作是确保每次构建都计算覆盖率并受到控制—它不能低于我设定的阈值。
无论阈值设置得多低,重要的是它是否受到控制。
这是交接前的最后一步。我必须配置一个持续交付流水线,以确保产品能够在一个点击中打包和部署。这是一个非常重要的—至关重要的—步骤。没有它,之前所做的一切和软件本身只是一堆文件。当一款软件能够在一个点击中打包和部署时,它才成为一个产品。
“流水线”意味着有一系列元素按顺序链接在一起;例如,对于一个Java应用程序:
Package JAR file
上传 JAR 文件到存储库
构建 JavaDoc 网站
将JavaDoc站点上传到Amazon S3
我使用Rultor来自动化整个流程并简化其启动、停止和日志记录。我只需在GitHub的任务中发布一个”请立即发布”的消息,产品就会在几分钟内打包和部署。
最后一步是交接——我必须向项目经理、项目的发起人和团队展示我的解决方案。每个人都必须接受它。这并不意味着他们会喜欢它,这也不是目标所在。目标是交付一个完整的解决方案,包括风险、假设、决策的记录,以及配置了持续集成、强制进行静态分析等等。如果我的解决方案不符合他们的标准,他们会更换架构师并重新尝试。
我的目标不是取悦他们,而是尽力满足要求,并根据我对问题和业务领域的专业理解做到最好。我之前写过关于这个问题的文章:《满足上司的快乐是虚假的目标》。再次强调,我的目标不是让他们开心,而是按照我对“完美”这个词的理解来制作一个完美的原型。如果我失败了,那就失败了。项目会找另一个架构师并重新尝试。
就是这样。框架已经准备好了,我的工作完成了。
Translated by ChatGPT gpt-3.5-turbo/42 on 2024-01-09 at 18:06