Scala的重新发明系统设计


35

很多很多月以前,我做过面向对象软件工程的硕士。我涵盖了所有内容:项目启动,需求,分析,设计,体系结构,开发等,等等。我一直以来最喜欢的IT书籍是《开发面向对象的软件》,这是一种基于经验的方法(IBM-1996)。由一群当时的真正专家创作的书。它描述了以工作产品为中心的方法来进行面向对象的分析,设计和开发方法。

我设计和开发游戏时感到很开心,并在游戏中处于顶峰,但我开始感到有些过时:敏捷运动已成为当今的时尚,并用新的时髦词重新命名了一些众所周知的迭代和增量方法。当我说“需求”或“架构”时,经验不足的开发人员开始皱眉,好像这些东西已被魔术所取代。

设计和开发失去了乐趣,我将把整个IT行业抛在身后。

然后我发现了Scala。哦,就像尘土飞扬的道路上的小雨。一切都变得清晰起来,空气再次变得甜美起来,硬盘驱动器上的微弱灯光将闪烁到深夜,使我开心地陪伴在发现世界中。

我喜欢系统设计。我是一名建筑师,而不是程序员。我喜欢分析,设计,思考,争论,改进-我只是喜欢简单,干净和清晰的设计。

我们如何设计纯Scala解决方案?

当然,常规命令图,交互图,对象图等都可以全部替换或改进,以将命令性和功能性面向对象的系统融合在一起。当然,有一个完全不同的地方可以开放!

我不是在寻找肿的复杂性,而是在寻找灵活的简单性。实际上,Scala很简单(虽然有所不同),但可以像一条瑜伽裤一样扩展以适应您的需求。对于这种可爱的语言,必须有一个设计系统,该系统应从简单开始,并且可以扩展以适合您的要求和您的领域。当然不能是UML!

那么我们如何设计纯Scala系统呢?想象一下,您有足够的勇气从头开始设计一个完整的系统,并且知道您将只使用Scala- 您的模型外观如何?您必须描述什么类型的图的结构和行为?您将如何对选项,匹配项,混合项,单例对象等进行建模,而不会陷入扩展现有建模技术而不是使用新颖,轻巧的创新工具集的复杂性。

是否存在这样的设计/过程/解决方案,或者现在该发明一个?


+1表示“可以扩展以适合您的需求,例如一条瑜伽裤。”
罗素

FWIW,我已经看到有关UML和Scala的问题,您可能会感兴趣。另外,如果您转到函数端,那么类别理论符号可能值得研究。格雷戈里·梅雷迪思(Gregory Meredith)通常会链接到一些有趣的演示文稿,尽管我不确定他博客中的内容-无论如何都应该值得一看。
Daniel C. Sobral

这很有价值;-)它为我提供了挖掘的方向。感谢Daniel
Jack

值得检查,Scala的“修改后” UML, github.com
mnn /

Answers:


12

我不能真正回答这个问题,但让我提出一些想法。

我是一所大学的计算机工程专业的学生,​​上学期我和一个小组进行了一个大型软件项目,而我们的主要语言是Scala。我们以传统方式进行设计:用例,领域模型,序列图,UML类图;通常的负载。而且,您已经知道,它们不太适合。这里有一些原因。

首先,考虑顺序图。顺序图的感觉是几个稳定的,长期存在的,半自治的参与者互相交谈。在Scala中,往往会在很短的时间内创建对象。此外,case类鼓励仅包含数据而没有代码的“哑”对象,因此传递消息的概念不合适。但是,时序图最困难的挑战是它们没有很好的方法来融合一流的功能。在OO设计中,仅在此处或那里使用回调就可以使它起作用,但是,如果这是转移控制的主要方式,则您会发现序列图几乎不反映您的实际代码。

UML类图更适合Scala。是的,mixin不能很好地通过,但是这可能是“调整”的,而不是需要全面检查的。但是我认为这可能会遗漏重点。

再次考虑为什么Scala具有“哑对象”。如果编写没有方法的案例类:

case class NewInvite(user: User, league: League)

不需要任何方法,因为成员的类型承诺您可以使用它们做什么。实际上,他们承诺一切。而且,在程序的每个作用域中,作用域内的变量及其类型都承诺有关代码在该位置的位置。

因此,也许,如果我们退后一步,而不是仔细考虑本身的类型,而是根据代码所处的环境以及对程序员(以及最终对用户的承诺)来设计程序。 ),在每种情况下,我们都会找到更适合Scala的描述。但是,我只是在这里猜测,我认为您是对的,在这一领域还需要探索更多。


“对程序员的承诺” –听起来很可爱;-)
杰克

12

UML摆脱了80年代末和90年代初的“泡沫战争”。它始终是符号的折衷,而不是设计方法。归因于UML的许多技巧秘密地是一种设计方法的结果,该方法表示“步骤1:绘制类模型”。

我建议我们不需要新的设计方法,而应该振兴一些旧的方法。从定义和分配职责开始,然后开始理解其实现,然后以良好的表示法和表示法传达这些想法。

职责范围

CRC在Scala中的工作原理与在其他任何OO语言中的效果一样好,也就是说它工作得很好!通过拟人化角色并了解他们的协作来对职责分配进行建模仍然很有效。

大规模地,您仍应使用对象进行设计。对象边界是封装边界。将可变状态和实现细节保留在这些对象边界内。案例类是良好的接口对象,香草集合和数据结构也是如此。

了解实施

跨对象边界而不是其他对象传递这些数据结构,您会发现a)将对象的胆量隐藏在内部更容易,并且b)对象行为的功能实现很有意义。

您不想将程序的大型结构视为“大量的小函数”。相反,您自然会偏向于系统隔离部分之间的狭窄接口。无论如何,它们的外观和感觉都非常像对象,因此请继续将其实现为对象!

表示法

因此,在这种设计结构中,您仍然需要摆脱您的想法并与他人共享。

我建议UML对于描述系统的大规模结构仍然有用。但是,它在较低的细节级别上无济于事。

细致入微,尝试识字编程技术。

我也喜欢使用minidocs。小型文档是一页或两页的文档,描述了系统行为的某些特定方面。它应位于代码附近,以便对其进行更新,并且应足够短以启用即时学习。达到正确的抽象水平需要一些实践:它需要足够具体才能有用,但又不能太具体以至于下一次代码更改会使它无效。


9

我们如何设计纯Scala解决方案?

我认为您对这个问题的看法有误。您的整体解决方案设计不应与特定语言绑定;相反,它应该对应于当前的问题。Scala的优点在于,它足够灵活,不会在实施设计时遇到麻烦。另一方面,Java会执行一些设计决策(主要是万事俱备),会使它感到有些笨拙。

在设计时,尝试显式建模状态。不变的数据和明确的状态导致设计容易进行推理。在大多数情况下,这会很好地转换为功能编程解决方案,尽管有时您可能会发现使用突变和命令式样式会更高效或更直接。Scala很好地容纳了两者。

Scala擅长摆脱困境的另一个原因是它具有创建满足您需求的DSL的能力。您可以制作自己的小语言以“正常”方式解决特定问题,然后在Scala中简单地实现它。

总而言之,我的主要观点是,您不应该尝试“设计纯Scala解决方案”。您应该以最自然,最易理解的方式设计解决方案,而Scala确实是一种非常灵活的工具,可以帮助您以多种方式实施这些解决方案。当您只知道一把锤子时,每个问题都开始看起来像钉子,因此请务必探索各种可用的方法。不要试图将设计过程限制在步骤X,Y和Z,以免错过A到W的可能性。


4

没错,OOD有限且过时。它的主要优点是它的定义和仪式都很好,每个琐碎的样式和设计技巧都有花哨的名称。对于更现代,更合理的系统设计方法,您将找不到任何可比的系统。

我只能列举两件事:一种语义设计方法,其子集称为“面向语言的编程”(Scala社区大量使用)和“域驱动设计”。后者是对更通用的语义设计方法的过于简化和面向对象的看法,只有喜欢OOD的人才能享受它。

我还建议您从其他函数式编程社区(例如Haskell和Scheme)那里学习一两个技巧,但并不是他们的所有设计知识都已经转移到了Scala。


3

问题是:我们如何设计纯Scala解决方案?

像这样的答案

  1. 我们之所以使用XYZ,是因为....
  2. 我们使用ABC,但是像这样....
  3. 我们开始使用XYZ,但后来发现ABC更好,因为...

可能表明这种工具集或解决方案已经成熟或被普遍接受。

关于是否应该考虑针对Scala的特定设计工具集的评论是另一回事。我对此的看法是,Scala在将命令性编程与函数式编程相结合的方式方面具有革命性意义。Scala捕捉了开发实践,概念和原则的惊人气息,因此,在找到最适合Scala的解决方案时,我们可能会发现一个解决方案,该解决方案的子集也可以很好地服务于许多其他语言。

可以肯定地说,我们知道后备问题的答案:

“ ...是时候发明一个了吗?”

难道?(“ yes ”没有规定解决方案的方法。可以通过扩展现有解决方案,也可以从头开始创建解决方案来实现。它确实承认,现有设计方案存在重大缺陷)

如果您不同意,欢迎您投票否决我的答案。我会像男人一样。


革命性的?为什么?Lisp几十年来一直是命令式和功能性方法的完美结合。由于其类型系统,Scala很有趣。
SK-logic

3
如果“革命性”是一个强有力的词,则表示歉意。我并不是在暗示Scala重新发明了轮子-但可以肯定的是,它确实重塑了橡胶。实际上,Scala很好地融合了许多编程语言的所有优点。我敢肯定,它也从Lisp大量借款。对我来说,我对其他人没有要求,Scala语言让我感到革命性,因为我以一种自然而然的方式来思考代码。相同的设计/建模语言肯定是免费的。那是我唯一的观点-对Lisp和所有其他出色的语言都没有侵犯。
2012年

2

我不会在语言之间进行区分,我会在Java中以相同的方式进行操作。但是我来自OO学校,而不是功能学校(代数Haskell之一或Lasp之一)。Haskell应用程序设计与Scala或Clojure确实不同。此外,由于有状态DOM,Scala.js应用程序的设计也有所不同-很难与应用程序的有状态部分抗衡。随着时间的流逝,我习惯于以OO方式进行操作,同时尝试尽可能消除状态和可变性。如果我是Typelevel或scalaz迷,我的应用程序看起来可能会大不相同。

  1. 确定技术堆栈和主要的设计决策,例如客户/服务器责任,分布式性质-我们真的需要数据持久性吗?什么最适合我们的应用?(当然还不是库或框架)

  2. 从一个App.scala我定义键类型(特征和案例类)和角色的文件开始-做AFK时需要考虑和思考。它在一个文件中时非常容易使用。如果它是一个简单的应用程序,那么甚至原型都可能诞生。我在此阶段花了很多时间,因为没有什么比拥有最正确,最主要的透明和明智的原型更为重要。所有这些都有助于使我们的进一步思考更加准确,并且增加了成功的可能性。

  3. 现在,当我们证明了解决方案的正确性,清晰的愿景并揭示了所有潜在的问题时,就该考虑引入哪种类型的第三方库或方法了。我们需要发挥框架还是仅需几个库?我们是要让这些有状态的演员吗,我们真的需要阿卡(Akka)的复原力吗,也许不需要?我们是否将Rx用于这些流?我们将UI用于什么,以便在拥有10种交互功能后不会发疯?

  4. App.scala文件分成多个文件,因为出现了新的特征和类,并且变得更大。他们的界面应该说明他们的角色。现在也应该考虑在可能的情况下利用类型类和即席多态性。这样,无论谁调用您的界面,它都必须对他们灵活。关键是仅实现常规功能,而将细节留给调用方。有时,您要实现的内容可能类似于Turing-complete,因此您应该为调用者提供一种自行实现细节的方法,而不是在GitHub上堆积功能请求。这些东西以后很难添加。必须从一开始就考虑周全。以及设计DSL。

  5. 因为应用程序会不断增长并且必须准备好承受新的功能,优化,持久性,UI,远程处理等,所以必须仔细考虑设计-这些东西尚未实现,或者以某种方式被模拟或抽象化。请记住,尽管所有内容都在一个文件中,但更改内容非常容易。散布到10多个文件中后,人脑就开始失去它。就像肿瘤在生长一样,这就是过度工程的开始。我注意到我的应用程序最后有一些长长的部分函数,​​这些函数构成了它的某种骨干。我发现使用起来很容易。我猜我被阿卡族演员感染了。

  6. 现在已经存在了第一个实际功能,而不仅仅是核心功能,现在该开始编写测试了。为什么这么晚?因为在这段时间里,您可能经历了重大的重写,而您却浪费了时间来维护这些测试。除非真的确定不会进行任何重大的重新设计,否则不应该编写测试。我更喜欢易于经受持续重构的集成测试,并且我不会花更多的时间测试实际开发的东西。我发生了几次,我花了太多时间来维护测试。修复潜在的错误肯定会带来更多收益,而不是不良测试。错误的测试或从一开始就编写的测试可能会导致严重的痛苦。请注意,我当然不是TDD的粉丝-恕我直言,这是胡说八道。

  7. 重构,使应用程序设计可读并保持其组件角色清晰。随着应用程序的增长,除非您是一个天才的程序员,否则就没有办法坚持下去,如果您读到此答案,您可能就不会这样做:-)我也不是。通常存在一些有状态的事情,因为您不知道如何避免那-可能导致问题的原因,请尝试消除它们。另外,请确保消除困扰您一段时间的代码异味。此时,您的项目经理(如果有)可能开始摇头。告诉他,这将还清。

  8. 完成,该应用程序设计合理吗?这取决于:组件是否具有定义明确的角色,这些角色甚至在您脑海中实际上仍然有意义?您能不花力气地接受其中一些并将其开源吗?他们之间是否有明确的关注点分离?您是否可以添加新功能而不必担心它会在其他地方崩溃(您的信号确切知道您在做什么)?一般来说,它应该像一本简单易懂地解决问题的书一样可读。恕我直言,这是好的设计的主要标志。

总而言之,这只是艰苦的工作,您可能无法从经理那里得到时间。我在这里描述的所有内容都需要大量时间,精力,咖啡,茶,尤其是AFK。您付出的越多,设计就越好。设计应用没有通用的方法,常识,实用主义,KISS,努力工作,经验意味着好的设计。

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.