谎言2:代码应该围绕世界模型进行设计吗?[关闭]


23

我最近读了《三大谎言》博客,我很难证明第二种谎言的正确性,在此引用:

(LIE#2)应该围绕世界模型设计代码

代码是虚拟世界的某种模型或地图,没有任何价值。我不知道为什么这个程序对某些程序员如此引人注目,但是它非常受欢迎。如果游戏中有火箭,请放心,有一个“火箭”类(假设代码是C ++),其中包含仅一枚火箭的数据并具有火箭的功能。完全不考虑实际上是在进行什么数据转换,也不考虑数据的布局。或就此而言,如果没有基本的了解,那就是哪里有一件东西,可能不止一件。

尽管这种设计会带来很多性能损失,但最重要的是它无法扩展。完全没有 一百枚火箭的成本是一枚火箭的一百倍。而且极有可能它的成本甚至更高!即使对于非程序员,这也没有任何意义。规模经济。如果您有更多东西,它应该会更便宜,而不是更贵。做到这一点的方法是正确设计数据并通过类似的转换对事物进行分组。

特别是这是我的问题。

  1. 作为虚拟世界的模型/地图,代码具有价值,因为对虚拟世界进行建模有助于(至少个人而言)可视化和组织代码。

  2. 对我来说,开设“火箭”课是一个完全有效的选择。也许“火箭”可以分为多种类型的火箭,例如AGM-114地狱火等,其中包含有效载荷强度,最大速度,最大转弯半径,瞄准类型等,但仍然每个发射的火箭都需要有一个位置和速度。

  3. 当然拥有100枚火箭弹的成本要高于1枚火箭弹的成本。如果屏幕上有100个火箭,则必须进行100次不同的计算才能更新其位置。第二段听起来好像是在宣称如果有100枚火箭,更新状态的花费应该少于100次计算?

我的问题是作者提出了一个“有缺陷的”编程模型,但没有提出“纠正”的方法。也许我在模仿Rocket类,但我真的很想了解这个谎言的原因。有什么选择?


9
@gnat:这个问题直接在软件设计范围内所以我倾向于给它一些余地。
罗伯特·哈维

12
那篇博客文章写得很差,并不能很好地捍卫和支持它的主张。我不会考虑太多。
whatsisname 2016年

21
谁写的引用都是白痴,对OO概念或如何在软件中实现几乎一无所知。首先,我们不是在映射到一个虚构的世界,而是在映射到现实的世界。而且,如果您有100枚火箭,则只有其他火箭的状态才会使用其他资源,而不会使用模型或行为。他似乎对此有不同的想法,并建议解决不存在的问题。“将相似的事物分组”作为一种优化有时可能有意义,但完全独立于是否使用类。如果您想学习,请避免使用这种骗子。
马丁·马特

3
考虑到作者不会费心地撰写总数超过5个段落来解释“ 3大谎言”,您可能比他花了更多的时间在思考这篇文章。如果他不愿意付出任何努力,您也不应该。
卡勒布

9
我认为他的意思是,您真的需要100个[可能也使用虚拟方法动态分配的]“火箭对象”,而不是位置列表,速度列表等(具有所有位置列表和速度列表意味着您可以使用矢量指令将速度添加到每个刻度更新上的位置,而不用通过对象列表编写幼稚的循环)
Random832,2013年

Answers:


63

首先,让我们看一下上下文:这是一位游戏设计师在一个博客上写的,主题是从Cell BE CPU看性能的最后下降。换句话说:它涉及控制台游戏编程,更具体地说是关于PlayStation 3的控制台游戏编程。

现在,游戏程序员是一群好奇的人,而控制台游戏程序员更是如此,而Cell BE是一个相当奇怪的CPU。(索尼为PlayStation 4采用更传统的设计是有原因的!)

因此,我们必须在这种情况下查看这些语句。

该博客文章中也有一些简化。特别是,这种谎言#2表现不佳。

我认为从某种意义上讲,从现实世界中抽象出来的一切都是模型。由于软件不是真实的而是虚拟的,因此它始终是抽象,因此始终是模型。但!模型不必在现实世界上具有清晰的1:1映射。毕竟,首先是使它成为模型的原因。

因此,从某种意义上说,作者显然是错误的:软件一个模型。期。从某种意义上说,他是对的:该模型实际上根本不必类似于现实世界。

我将提供一个示例,这些年来,我已经在其他一些答案中给出了(著名的OO 101银行帐户简介示例)。这是几乎几乎所有OO类中的银行帐户的外观:

class Account {
  var balance: Decimal
  def transfer(amount: Decimal, target: Account) = {
    balance -= amount
    target.balance += amount
  }
}

所以:balance数据transfer操作

但!这是几乎几乎所有银行软件中的银行帐户的外观:

class TransactionSlip {
  val transfer(amount: Decimal, from: Account, to: Account)
}

class Account {
  def balance = 
    TransactionLog.filter(t => t.to == this).map(_.amount).sum - 
    TransactionLog.filter(t => t.from == this).map(_.amount).sum
}

因此,现在transfer数据balance是一个操作(在事务日志上的左折)。(您还将注意到,它TransactionSlip是不可变的,balance是一个纯函数,TransactionLog可以是仅附加的“几乎”不可变的数据结构……我敢肯定,很多人在第一个实现中发现了明显的并发错误,现在这些错误已神奇地消失了)

请注意,这两个都是模型。这两个都是同等有效的。这两个都是正确的。这两个模型都是一样的。但是,它们彼此之间是完全对偶的:一个模型中的所有数据都是另一模型中的操作,而一个模型中的所有操作则是另一模型中的数据。

因此,问题不在于是否要在代码中建模“真实世界”,而是如何建模。

事实证明,第二种模式实际上也是银行在现实世界中的运作方式。正如我暗示上方,这第二个模型主要是不可改变的,纯净,不受并发错误,这实际上是,如果你认为有一段时间不是很久以前,这里很重要的TransactionSlip小号是实际已发送各地的纸条通过马车。

但是,第二个模型实际上与现实世界银行的工作方式和现实世界银行软件的运作方式都匹配,这一事实并不能自动使它更“正确”。因为实际上,第一个(“错误”)模型相当接近银行客户如何看待他们的银行。对他们来说transfer是一项操作(必须填写表格),并且balance是他们帐户对帐单底部的一条数据

因此,很可能在高性能PS3射击游戏的核心游戏引擎代码中没有Rocket类型,但是即使模型看起来很怪异,仍然会有一些世界的建模过程。给那些不是游戏机物理引擎编程领域专家的人。


1
这是否意味着好的代码为现实世界建模,而实际上仅仅是对现实世界的误解导致了不好的模型,从而导致了不好的代码?
yitzih's

14
我宁愿将其表述为“有时,现实世界不是您认为的那样”或“什么是'现实世界'取决于上下文”。(同样,对于银行帐户所有者,其对帐单底部的数据非常真实,而对银行雇员而言,则是短暂的。)我认为博客文章中的对帐单主要是由于作者不了解“建模“现实世界”并不意味着“拍摄照片并把在那里看到的一切都变成一类”。
约尔格W¯¯米塔格

您的在线银行应用程序的前端可能会将balance数据视为交易,将交易视为更多数据,并将传输视为操作,因为这是用户看到的,即使后端可能会有所不同。
user253751 '16

@yitzih:每个模型都是抽象和简化的,因此您可以指责每个模型都是错误的,但这并不是建设性的。每个模型都必须实现一个目标,并且必须对此有足够的优势,而不能浪费不必要的资源。对于政府的软件,人员可能是可能参加选举,必须纳税或可以与另一人结婚的人;对于我们的CRM软件,人员是与订单相关联并且具有交货地址的人员(而模仿他吃的方式)…
Holger

2
如果人们对银行业务一无所知,他们会发现第二步变得容易,并且由于他们所知道的银行业务技术是为了使银行业务有效而发明的,因此他们可以使银​​行软件有效。不是因为第二种模型“更像现实世界”,而是因为它描述了更好的银行。第一个模型可能是现实世界中功能失调的银行的准确表示!猜猜是什么:如果您想要好的银行软件,那么程序员仅从需求文档中就需要学习如何做好银行业务。
史蒂夫·杰索普

19

我不同意他提出的每一个“谎言”。

TL; DR本文的作者试图引起争议,以使他们的文章更加有趣,但是所谓的“谎言”被软件开发人员接受是有充分理由的。

谎言1-大O对于扩展目的很重要。没有人会在乎一个微小的应用程序是否需要更长的时间(这是唯一的时间常数),他们会在乎将输入大小加倍时,执行时间不会乘以10。

谎言2-在现实世界中对程序进行建模后,程序员可以在3年后查看您的代码以轻松了解其功能。代码需要是可维护的,否则您将需要花费数小时来试图了解程序的意图。另一个答案建议您可以有更多通用类,例如LaunchPadMassiveDeviceMover。这些不一定是糟糕的课程,但您仍然需要该Rocket课程。任何人都应该如何知道某事MassiveDeviceMover或其动作?它在移动山脉,宇宙飞船还是行星?从根本上讲,这意味着添加类似的类MassiveDeviceMover会使您的程序效率降低(但可能会更易于阅读和理解)。

此外,很久以前,开发人员的时间成本就开始超过硬件成本。开始尝试在思想的最前面进行优化设计是一个可怕的想法。您可以通过简单易懂的方式对其进行编程,然后在找出程序的哪些部分需要大量时间才能运行之后对其进行调整。别忘了:20%的程序正在使用80%的执行时间。

谎言3-代码非常重要。编写良好(和模块化)的代码可实现重复使用(节省无数工时)。它还允许您筛选和识别不良数据,以便对其进行处理。数据很棒,但是如果没有代码,就不可能分析和获取有用的信息。


5
我认为我更同情#3。在30年的编程中,我发现的绝大多数错误,性能问题和其他问题都可以通过修复数据表示来解决。如果数据正确,则代码实际上会自行编写。
李·丹尼尔·克罗克

6
#3的真正问题在于,它比较了苹果和橙子,而不是代码比数据更重要,反之亦然。
布朗

3
输入数据不在您手中,但是如何在软件中表示数据完全在他们手中。您可能会称其为“编码”的一部分,但我认为并非如此:例如,它通常独立于语言,并且通常在任何编码开始之前就已完成。我同意,但是,清理丑陋的输入数据的代码通常是一件好事。但您必须先定义“干净”,然后才能这样做。
Lee Daniel Crocker

3
实际上,我认为第三谎言根本不是谎言。弗雷德·布鲁克斯(Fred Brooks)几十年前已经写过:“给我看您的流程图并隐藏您的表格,我将继续感到困惑。向我展示您的表格,我通常不需要您的流程图;它们将显而易见。” (现在,我们可能会谈论“算法”和“数据类型”或“方案”。)因此,数据的重要性早已众所周知。
约尔格W¯¯米塔格

1
@djechlin我的意思不是说数据不重要或者代码更重要。只是,数据并不比代码重要。它们都是非常重要的,并且相互依赖。
yitzih

6

在电子商务系统中,您没有在类级别上处理“火箭”,而是在处理“产品”。因此,这取决于您要完成的工作以及所需的抽象水平。

在游戏中,可以说火箭只是许多类型的“运动物体”之一。它们与所有其他移动物体一样,都具有相同的物理性质。因此,至少,“火箭”将继承一些更通用的“运动对象”基类。

无论如何,您引用的文章的作者似乎夸大了他的论点。火箭仍然可以具有独特的特征,例如“剩余燃料量”和“推力”,并且对于一百个火箭,您不需要一百个类来表示它,您只需要一个。在大多数体面的编程语言中,对象创建的成本相当低,因此,如果您需要跟踪类似火箭的事物,那么就不应该制作Rocket对象,因为它可能太昂贵了这一想法没有多大意义。


5

现实世界中存在的问题全是物理学。我们将事物分解为现实世界中的物理对象,因为它们比单个原子或可能是火箭的巨大熔融渣更容易移动。

同样,现实世界提供了许多我们依赖的有用功能。企鹅例外确实很容易-“除了...以外,所有鸟类都在飞。” 将事物标记为火箭真的很容易,我的意思是,如果我称那只企鹅为火箭并点燃它……那是行不通的。

因此,我们如何在现实世界中分离事物的概念在这些约束下起作用。当我们在用代码处理事务时,我们应该将事情分开,以在绝对不同的那些约束下正常工作。

有什么选择?

考虑网络。我们不在代码中对端口,电线和路由器建模。相反,我们将网络通信抽象为连接和协议。我们这样做是因为无论实际情况如何实现,它都是有用的抽象。而且它提出了一些有用的约束(例如:只有打开连接后才能进行通信),这些约束只在代码中起作用

所以是的,有时在现实世界中可以对代码进行建模,但这只是一个巧合。当人们谈论OOP时,对象不是真实世界的对象。学校和辅导老师说,否则将是数十年的悲剧。


1
+1:协议在很大程度上是“现实世界”的抽象。例如,即使在当今世界,礼宾人员也是进行国事访问的一些最重要的人员。在八国集团会议上,谁先走上奥巴马还是普京的红毯?他们拥抱还是握手?我如何问候阿拉伯人和印度人?等等。我们在“现实世界”中拥有大量的“事物”,而在“物理世界”中则不是“事物”。对现实世界进行建模并不意味着对物理世界进行建模。即使不是Rocket在这个家伙的代码类型,我愿意打赌,有仍然是一些模型...
约尔格W¯¯米塔格

…真实世界,即使它不对应任何“物理”(在“可触摸”的意义上)。不过,我在其中找不到实际的“物理”对象(就“物理学家可能认识的事物”而言)也不会感到惊讶,例如四元数,张量,场等,它们当然 “现实世界的事物”和“现实世界的模型”。
约尔格W¯¯米塔格

艾伦·凯(Alan Kay)设想了Dynabook是一台用于婴儿出生的计算机,它将成为他们大脑的扩展。那么,MVC模式的目的是使View和Controller弥合大脑与Model之间的鸿沟,以支持Direct Manipulation Metaphor(即直接操纵隐喻),即对计算机只是大脑的扩展的错觉,用自己的思想直接操纵模型对象。这就是我们所说的领域模型为“现实世界”建模时的意思。它应该在我们的大脑中实现抽象。
约尔格W¯¯米塔格

当我想到一个主机游戏物理引擎时,我可能确实没有想到火箭,因此在我的代码中不应该有一个火箭模型。但是我可能正在考虑其他一些“现实世界的想法”,并且代码中应该这些想法的模型。
约尔格W¯¯米塔格

2

另一种方法是对程序关心的事物进行建模。即使您的程序处理的是火箭,也可能不需要一个称为的实体Rocket。例如,您可能有一个LaunchPad实体,一个LaunchSchedule实体和一个MassiveDeviceMover实体。所有这些都有助于发射火箭这一事实并不意味着您自己就在处理火箭。


0

我的问题是作者提出了一个“有缺陷的”编程模型,但没有提出“纠正”的方法。也许我在模仿Rocket类,但我真的很想了解这个谎言的原因。有什么选择?

这是真正的问题,但是我会以开发人员的身份介绍给您,也许会有所帮助。

首先,我不会将其中的任何谎言称为普遍的误解。称其为谎言只是炒作。

一个他是正确的,在某些方面。不要花很多时间在这上面,因为这不是问题的一部分。但本质上他是正确的。我可以重申一下:“在实验室中起作用的东西在现实生活中可能行不通”。开发人员坚持使用在“实验室”中运行但在实际应用中失败的设计的次数太多了。

“三声”对我来说有点肥皂泡沫,但本质上他还是对的。但这可以重写为“围绕您的需求编写代码,不要试图使您的需求适合您的代码”。

再说两次,这里他是正确的。我看到开发人员花了数周甚至更长的时间来开发一个“火箭”类,而一个简单的“车辆”类甚至是一个更简单的“可移动”类都可以使用。如果您只需要从屏幕的左侧向右侧移动火箭并发出声音,那么您可以使用与汽车,火车,轮船和苍蝇相同的类。100的成本应该小于1 * 100的参数,这似乎是在开发上花费的时间,而在计算成本上却没有那么多。尽管坚持使用较少的可重用的通用类是“便宜的”,但许多无法重用的特定类。可以将其改写为“通用类要比特定类好,

从本质上讲,整个文章可以用更少的流行语重写,并且充其量只是一段长篇文章。就是说,这是一篇针对狭窄编程领域的博客文章。我已经做了一些嵌入式编程,并且我同意这些声明背后的一般想法,尽管它们周围有很多“绒毛”以使其适合在GDC上进行演示。

最后一点,这篇文章写于2008年(据我所知)。事情变化很快。这些说法在今天是正确的,但是嵌入式系统在那时比那时更普遍,并且开发模式也在改变。也许甚至响应这篇文章/演讲。


-1

我发现有趣的是,这些都围绕着学术关注点:平台,内存使用效率和数据。但是它完全忽略了人为因素。

软件就是要满足人们的需求。通常,这是从业务角度进行量化的-有些客户想要一些东西,一些支持者愿意为此付出代价。如果以满足等式两边需求的方式编写软件,则它是好软件,如果不是,则是坏软件。

如果平台对客户不重要,那么平台就不重要。如果内存效率对客户不重要,那么它就不重要。如果数据对客户不重要,则数据不重要。如果代码可以正常工作,但无法读取或维护,并且客户希望以合理的价格进行快速,可靠的更改,那么编写不好的代码将是一件坏事。如果代码可以工作,但是无法读取或维护,并且客户不在乎或不愿为昂贵的重构付费,那么编写不好的代码将是一件好事。

最大的谎言是,除了人的因素之外,其他任何事情都不重要。为什么数据很重要?因为有一些需要它的客户或利益相关者。那就是“大真理”。


4
不幸的是,客户需要快速,肮脏的代码,这些代码易于阅读和维护,廉价,无需测试且没有错误。
Gonen I

@ user889742哈!真正。你已经准确地说工程问题建筑师们一直试图解决所有的时间,是什么让这个行业这样一个有趣的工作空间英寸
价格-琼斯

他忽略了人为因素,因为他是一名游戏开发人员,并且游戏的维护期限相对较短,尽管今天比2008年更长。今天的第一天补丁似乎是游戏的常态。早在2008年,游戏补丁仍然相对稀少。
RubberDuck

-1

恕我直言,如果代码是“围绕世界模型设计的”,则对于设计人员,开发人员以及维护人员而言,都更容易理解。但是我认为不仅是我,而且不仅仅是软件。摘自Wikipedia:科学建模是一项科学活动,其目的是通过参考现有的和通常公认的知识,使世界的特定部分或特征更易于理解,定义,量化,可视化或模拟

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.