设计自上而下还是自下而上是更可取的?


31

据我了解,自上而下的设计是通过将抽象的高级概念细化为较小的混凝土和易于理解的部分,直到定义了最小的构建块。另一方面,自下而上定义了较低级别的部分,然后逐渐构建较高级别的块,直到形成整个系统。

实际上,最好将两种方法结合起来:从高级规范开始,以完全指定领域知识,其关系和约束。一旦清楚地了解了问题,便会创建最小的构建块来构建系统。

过程:

  • 创建需求规范
  • 创建设计规范(带有图表)
  • 实行
  • 交付
  • 重复(在迭代开发中,我们不重复每个阶段的全部工作,而是重复每个阶段的一点工作,并每天开会以满足客户的动态需求)

看起来对我来说是完全正常的(以规格作为计划)。它有其缺陷,但这就是为什么我们要进行迭代开发:为什么要花时间在一个阶段上,而不是花时间进行需求分析,以研究领域知识中可能发生变化的所有可能事物(可能每天),而是进行一些分析,进行一些设计然后实施。

另一种方法是,每次迭代都是一种小型瀑布式的方式,在几天(或一周)内进行分析。设计同样如此。其余时间用于实施。自上而下的方法与迭代开发相结合有天生的错误吗?

Paul Graham 在他的论文《自下而上编程》中似乎鼓励完全自下而上地构建,或者鼓励自下而上地对其进行编程,但是不建议进行需求分析/设计阶段:

经验丰富的Lisp程序员对程序进行了不同的划分。除自上而下的设计外,它们还遵循可称为自下而上的设计的原则-更改语言以适应问题。

就我所知,他的意思是Lisper仍然执行自上而下的设计,但程序自下而上,是真的吗?他写道:

值得强调的是,自下而上的设计并不意味着仅以不同的顺序编写相同的程序。自下而上工作时,通常会得到不同的程序。您将获得一种具有更多抽象运算符的更大语言,以及一个用它编写的较小程序,而不是一个单一的整体程序。取而代之的是门,而不是门。

这是否意味着在用Lisp编写程序期间,您最终会获得通用工具?


也许,如果您提供指向您所引用的书,论文或文章的链接,则其他人可能就您的问题做出更合理的陈述。另外,不清楚您的问题具体针对什么。您是在寻求有关您要实现的设计的某些细节方面的建议,还是询问有关您所引用的文章的问题。如果您将其分解成几个分别问到的问题,也许会更容易阅读和回答。
S.Robins

@ S.Robins总而言之,我展示了我通常的自上而下的软件开发方法。但是,有些人更喜欢自下而上,其中之一就是著名人士Paul Graham。我要求了解自下而上的设计对总体的帮助,特别是在Lisp中,因为它具有鼓励的特殊功能(如Paul建议)。
Amumu

1
Amumu我已经编辑了示例,因为无论如何这两个答案似乎都跳过了它。请尽量简化您的问题,每个问题只问一个问题
yannis 2012年

Answers:


35

自上而下是描述您所知道的事物或重新构建已经构建的事物的好方法。

自上而下的最大问题是,很多时候根本没有“顶”。在开发系统和探索领域时,您将改变对系统应该做什么的想法。您不知道的事情(例如,您希望系统执行的操作)如何成为起点?

自上而下的“本地”是一件好事……在编码之前进行一些思考显然是件好事。但是,进行过多的思考和计划并非如此,因为您所设想的并不是真实的情况(除非您之前曾去过那里,即如果您不是在建造而是在重建)。在构建新事物时,全局自上而下只是胡说八道。

自下而上应该是(全局)方法,除非您知道问题的100%,您只需要对已知的解决方案进行编码,而不必担心寻找可能的替代解决方案。

Lisp方法是蒸馏自下而上的。您不仅可以自底向上构建,还可以按照需要的方式对砖进行成形。没有什么是固定的,自由是全部。当然,自由需要承担责任,并且您可以通过滥用这种力量来制造可怕的事情。

但是可怕的代码可以用任何语言编写。甚至在那些被设计成心灵笼子的语言中,也希望猴子能够使用这些语言来启动并运行良好的程序(这个想法在很多层面上都是错误的,甚至在思考时也会受到伤害)。

您的示例是关于Web服务器的。现在在2012年,这是一个定义明确的问题,您需要遵循一些规范。Web服务器只是一个实现问题。尤其是如果您要编写与现存的其他数百万个Web服务器基本相同的Web服务器,那么除了一些细节外,没有什么是真正不清楚的。即使您对RSA的评论仍在谈论具有正式规范的明确定义的问题。

有了一个定义明确的问题,有了正式的规范和已知的解决方案,那么编码就成了问题的根源。自上而下可以。这是项目经理的天堂。

然而,在许多情况下,没有经过验证的众所周知的方法可用于连接点。实际上,即使是什么点也很难说。

例如,假设要求您指示自动切割机将要切割的零件对准与理论上的重复徽标不完全一致的印刷材料。您将获得机器拍摄的材料零件和图片。

什么是对齐规则?你决定。什么是模式,如何表示?你决定。如何对齐零件?你决定。零件可以“弯曲”吗?这取决于,有些不是,有些是,但是当然不会太多。如果材料变形太大而无法将其切割成可接受的形状怎么办?你决定。所有材料卷是否相同?当然不是,但是您不能使用户烦恼以适应每个卷筒的对齐规则……这是不切实际的。相机在看什么照片?这种材料,无论可能意味着什么……可以是颜色,也可以是黑底黑字,只有光反射才能使图案清晰可见。识别模式什么意思?你决定。

现在尝试设计用于此问题的解决方案的一般结构,并以金钱和时间给出报价。我敢打赌,即使您的系统架构...(是的,架构)也将是错误的。成本和时间估算将是随机数。

我们实施了它,现在它已成为一个工作系统,但是对于系统的形状,我们改变了很多次。我们添加了整个子系统,现在甚至无法从菜单访问它们。我们多次切换了协议中的主/从角色。也许现在我们已经掌握了足够的知识,可以尝试对其进行更好的重建。

当然,其他公司也确实解决了相同的问题...但是,除非您是其中一家公司,否则您自上而下的详细项目很可能是在开玩笑。我们可以自上而下设计它。您不能因为以前从未做过。

您可能也可以解决相同的问题。自下而上地工作。从您了解的内容开始,学习您不了解的内容并加总。

新的复杂软件系统正在增长,而不是设计的。时不时有人开始从头开始设计一个大型的,新的,不正确的复杂软件系统(请注意,对于一个大型的,复杂的软件项目,只有三种可能性:a]规范是模糊的,b]规范是错误的并且自相矛盾。或c]两者...,最常见的情况是[c]。

这些是典型的大型公司项目,仅PowerPoint幻灯片和UML图就投入了成千上万小时。在消耗大量资源后,它们总是会完全失败……或者在某些非常特殊的情况下,他们最终会交付价格过高的软件,仅能实现最初规格的一小部分。而且该软件总是会受到用户的深深憎恨……不是您要购买的那种软件,而是您因为被迫购买而使用的那种软件。

这是否意味着我认为您应该只考虑编码?当然不是。但是在我看来,构造应该从底部开始(砖块,具体代码),并且应该向上发展……并且您对细节的关注和关注应在某种程度上“淡化”,因为您与现有产品的距离越来越远。自上而下的呈现方式常常是好像您应该一次对整个系统投入相同级别的细节:只是保持它分裂每个节点,直到一切都变得显而易见为止……在现实中,子系统是从子例程中“成长”的。如果您对特定问题没有经验,那么子系统,模块或库的自上而下的设计将是可怕的。一旦知道要插入的功能,就可以设计一个好的库,而不必相反。

Lisp的许多想法正变得越来越流行(一流的函数,闭包,默认的动态键入,垃圾回收,元编程,交互式开发),但是Lisp仍然在当今(在我所知道的语言中)在塑造代码方面非常独特。满足您的需求。

例如,关键字参数已经存在,但是如果不存在,则可以添加它们。我为我正在尝试的玩具Lisp编译器完成了此工作(包括在编译时进行的关键字验证),并且不需要太多代码。

相反,使用C ++最多可以得到的是一堆C ++专家,他们告诉您关键字参数不是那么有用,或者说是一个非常复杂,破碎,半支持的模板实现,实际上并没有那么有用。C ++类是一流的对象吗?不,您对此无能为力。您可以在运行时或编译时进行自省吗?不,您对此无能为力。

Lisp的这种语言灵活性使其非常适合自下而上的构建。您不仅可以构建子例程,还可以构建语言的语法和语义。从某种意义上说,Lisp本身是自下而上的。


是的,我的意思是本地自上而下。您不断完善需求,并通过迭代不断发展。自上而下的要点是我们要使代码结构尽可能正确。不断重构。例如,起初您想将字符串传递给函数,但是后来您需要一个完整的类来满足更改,并且如果该函数已经在所有地方使用,则重构将非常耗时。
Amumu

要以已知的方式重建已知的内容,则自上而下即可。您的网络浏览器就是一个例子。即使使用像C ++这样的语言来迫使您预料并正式指定(使用可怕的语法)程序中所有变量所包含的值的所有类型,仍然是可行的选择。
6502

我认为我的示例不是一个众所周知的方法,因为我认为它是由具有数据通信基础知识的人制作的,而C ++代码是基于领域知识(在本例中为网络)进行非常基础的设计的结果。这是进化设计的一个例子。在下一次迭代中,开发人员将学习有关领域知识的更多信息,因此他将自己的设计扩展到适合不断发展的知识(例如Web服务器每一层中增加的安全性方面)。然后,他根据新设计改进了实现。
Amumu

这里的重点是从领域独立的语言(例如自然语言,数学语言...取决于领域)派生的实际解决方案。编码行为只是从生成的解决方案到代码的详细映射。
Amumu

1
嗨Mr.6502。一年后,随着我学到更多东西,我开始看到您所说的变得真实。我写了另一个线程:programmers.stackexchange.com/questions/179000/…。如果您有时间,我希望您能再次阐明我的想法:D。
Amumu

6

我不确定这个答案如何适用于Lisp,但是我刚读完《敏捷原理,模式和实践》,作者叔叔鲍勃Uncle Bob)强烈提倡C#的自上而下的方法(也适用于C ++),我完全使用它。同意。

但是,与其他一些得出结论的结论不同,即自上而下的方法意味着在第一次交互中您仅交付文档和总体设计,而本书指出了另一种方法:TDD与演化设计相结合。

这个想法是,您从顶部开始,定义最高的抽象级别(或局部最高的抽象级别),并在定义它们之后立即使它们进行有用的工作,从而使功能#1立即起作用。然后,随着您添加越来越多的功能,您可以重构代码并根据需要发展设计,同时始终了解SOLID原则。。这样,您将不会以太多的抽象层结束,也不会以不适合整体架构的低层设计结束。如果您不确定这是什么意思,那么我上面提到的那本书有一整章的内容,其中有一个示例,其中开发人员采用了一些概念,并从UML图和低级类入手,只是意识到一旦真正开始编码,就不需要这些类的一半。本质上,使用这种方法时,随着项目中定义了更多高级细节,自然会降低低级代码的数量。

最后,如果您练习SOLID,则不应遇到定义了高级抽象然后又进入细节的情况,突然发现没有OOD或抽象的情况。这不是自顶向下设计的真正错,而是懒惰的工程。

如果您有兴趣阅读有关XP和进化设计的更多信息,请阅读另一位伟大的作者Martin Fowler的精彩读物:“ Design Dead是否死了?”


完善。这就是我在说的。也很高兴了解SOLID原理,并且Agile支持自上而下的方法(我认为使用TDD和XP,人们会尽快使用代码并避免编写文档)。
Amumu '02

@Amumu:我在Fowler撰写的另一篇文章中添加了链接。您可以详细了解没有设计/文档的牛仔编码与XP / Agile实际推广的区别。就个人而言,我坚信文档很有价值,并且我一直在积极地促进我的团队跟上我们的设计文档,但是我一直在逐渐转移看法。现在,我们的“设计任务”都是白板/餐巾纸,而实际文档仅在编写故事后才更新。我们也一直在缩减实际记录的内容,因此文档仅涵盖高级/体系结构方面的内容
DXM 2012年

3

对我来说,保罗·格雷厄姆(Paul Graham)在他的文章中最重要的评论是:

Lisp程序员遵循的原则可以称为自下而上的设计,即更改语言以适应问题。在Lisp中,您不仅要按照语言编写程序,还要按照程序编写语言。

或者,正如C ++界所知道的那样:库设计就是语言设计(Bjarne Stroustrup)

自上而下设计的主要思想是:首先计划,然后编码。Beanow在编写时是正确的,当可执行代码迟到时会出现问题。在自下而上的设计中,您总是有代码,并且该代码可以被测试。

另外,您的代码也不平坦。我的意思是,它倾向于具有更多级别的较小抽象。在自上而下的设计中,人们通常最终会沉迷于任意程度的抽象之下,甚至根本没有抽象。自下而上设计的OTOH代码通常包含较少的底层控制和数据结构,因为它们很可能被抽象掉。


2

理想情况下,用任何一种语言(不仅是Lisp)编写程序,都可以编写一整套通用工具,这些工具可以帮助您加快下一个程序的速度,或者加快对当前程序的增强。

实际上,跟踪这些工具可能很困难。文档很差而且杂乱无章,了解它们的人离开了。在实践中,重用代码通常比其价值更大。但是,如果对代码进行了适当的记录和组织,并且程序员坚持住了(或保留了自己的有用代码的供应),则可以通过从仓库中获取代码而不是重新构建代码来节省大量工作。

所有设计都必须自上而下,否则您将不知道自己在做什么。您要盖棚子还是汽车?您无法找出具有自底向上设计的产品。但是,如果您要构建左前轮,则可能会认为此项目和其他项目以后都可能需要更多的前轮。而且,如果您制造了一个可重复使用的轮子,那么您将以一个价格购买四个轮子。(以及您接下来要建造的那辆拖车的18,全部免费。)

请注意,与真实的汽车和真实的车轮不同,如果您构建了一个软件“车轮”,那么您将构建无限数量的软件。

关于自上而下的设计的更多信息:虽然您必须从头开始设计,但我不得不指出,如果您无法制造轮毂,则需要在进行大量工作之前先找到答案。因此,您确实需要自下而上地进行工作,而自上而下地进行工作。另外,知道自己可以建造轮子可能会建议很多您以前没有想到的项目,例如拖挂车。我认为自上而下的方法有主宰,但有一个轻的触摸。

因此,要继续改写Paul Graham的话,理想情况下,在编写程序时,您会得到很多可重复使用的部件,这些部件可以在原始程序和其他程序上节省大量时间。为了使自己与Paul Graham保持一点距离,这可以使用任何语言(尽管有些鼓励比其他鼓励更多)。

这个几乎不可思议的过程的敌人是具有短期前景的程序员。这可能是由于人格缺陷造成的,但更常见的是由于在公司内部以及在雇用公司之间太快地调换工作和责任。


哇,我从2月16日起得到回复,收件箱中没有显示任何内容。我同意您的看法,始终需要制定一个计划,甚至是一个非常简单的计划,然后再制定一点,然后完善计划。这是完全合理的。但是,我似乎不明白为什么许多开发人员喜欢在编写代码时计划所有事情。可以节省大量时间,他们可以集中精力在实际问题上做更多的工作,而不是在实现过程中不断重构代码体系结构。
Amumu '02

@Amumu编写时进行计划的原因之一:有些程序员非常了解他们在做什么,而键盘和鼠标是他们的编码瓶颈,而不是计划过程。(可重用的代码将使他们能够进行新的创造性工作,从而使思考再次成为艰难的部分。)
RalphChapin 2012年

@Amumu写作时要计划的另一个原因:语言与某些编程员/问题的组合,是思考问题的最佳方法。语言是将您的想法写在“纸上”的最简单方法。我只在这里和那里读过一些有关Lisp的内容,但是我认为这对于这特别有用。(某些语言最初是用来表达问题的方法,而不是让机器来解决问题的方法。)如果重新排列代码与思想一样容易,那么编写代码也可以。但是程序员,语言和问题必须很好地融合在一起才能起作用。
RalphChapin 2012年

这是可靠的。规划的思想是将领域概念之间的领域知识和关系标识与实际编程过程分开。想象一下,如果一个类被其他数十个类使用,而没有一个很好的计划来创建与其他类进行通信的接口,那么我们可以轻松地进行集成和重构。在这种情况下,浪费在打字和重组代码上的时间大大超过了在纸上进行识别所花费的时间。
Amumu

让我们举个例子:假设我们正在编写一个客户端/服务器应用程序。首先,我们必须通过定义交换消息来识别协议,确定消息在内存中的布局方式(即标头16字节,id 4字节...。)。图不等于建模/规划:abstratt.com/blog/2009/05/03/on-code-being-model。我们不需要使用UML或图进行建模,因为UML几乎只专注于OOP,并且在UML类图中键入所花费的时间不少于键入实际代码,甚至更多。
Amumu

1

自顶向下方法与迭代开发相结合有什么问题?

在迭代过程中使用自顶向下方法进行迭代开发不会交付任何有效代码。它提供设计文档,客户很难提供反馈。诸如“是的,我想(这对我来说太抽象了)”之类的回答将无法帮助您进一步指定客户所需系统的细节。

取而代之的是,您仅创建所请求内容的一般概述。(要求)用作定义成品和应优先实现的产品的一般准则。从那里开始,您可以为每次迭代创建有效的产品,客户可以实际使用这些产品,看看这是否是他们的初衷。如果不是这样,那么这将不是问题,因为没有花费数百个小时来设计一种与客户现在所要求的系统不同的系统。

保罗·格雷厄姆(Paul Graham)鼓励完全从下而上建造吗?还是只是从下至上编程,而不是从需求分析/设计阶段开始?

我不会为别人说话。我也没看过他的论文。我给你的答案来自我的学历和经验。

这意味着,在用Lisp编写程序的过程中,您最终得到了一个通用工具,可以用来编写与原始程序类似的程序,不是吗?

使用这种方法意味着您将获得更多抽象的构建块。仅仅因为您将必须构建可立即显示的独立子系统,所以您无法创建紧密联系且必须一次实施的自上而下的设计。它提高了可重用性,可维护性,但最重要的是,它可以更灵活地响应更改。


1
在我眼中,当您已经在引用区块时,不需要
大胆的表情
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.