Answers:
难道没有任何变数真正地操纵状态吗?
是。在C ++中,唯一可变的东西是(非const
)变量。
“您应该处理很少的状态”是什么意思?
程序的状态越少,则越容易理解其功能。因此,您不应该引入不需要的状态,并且一旦不再需要它就不要保留它。
在像C ++这样的不纯语言中,状态管理不是您真正在做什么吗?
在像C ++这样的多范式语言中,通常可以在“纯”功能或状态驱动的方法之间进行选择,或在某种混合形式之间进行选择。从历史上看,与某些语言相比,对函数式编程的语言支持一直很薄弱,但是正在改善。
除了限制变量生存期外,还有什么其他方法可以“以很少的状态进行交易”?
限制范围和寿命,以减少对象之间的耦合;偏爱局部变量而不是全局变量,并且偏爱私有对象而不是公共对象成员。
状态表示某些内容存储在某处,以便您以后可以参考。
创建一个变量,为您创建一些空间来存储一些数据。此数据是程序的状态。
您可以用它来做事,修改,计算等等。
这是状态,而您所做的事情不是状态。
在函数式语言中,您通常只处理函数并将函数作为对象传递给周围。尽管这些函数没有状态,并且将函数传递给周围,却不引入任何状态(可能在函数本身内部)。
在C ++中,您可以创建函数对象,它们struct
或class
类型已operator()()
超载。这些功能对象可以具有局部状态,尽管不一定要在程序中的其他代码之间共享局部状态。函子(即函数对象)很容易传递。这几乎可以模仿C ++中的功能范例。(据我所知)
几乎没有状态或没有状态意味着您可以轻松地优化程序以并行执行,因为线程或CPU之间无法共享任何内容,因此无法创建争用,也无需防止数据争用等。
状态只是存储的数据。每个变量实际上都是某种状态,但是我们通常使用“状态”来指代在操作之间保持不变的数据。作为一个简单,毫无意义的示例,您可能有一个内部存储an int
和has increment()
和decrement()
member函数的类。在这里,内部值是状态,因为它在此类实例的生存期内一直存在。换句话说,该值是对象的状态。
理想情况下,类定义的状态应尽可能小,并且冗余最少。这有助于您的班级满足单一责任原则,改善封装并降低复杂性。一个对象的状态应该由该对象的接口完全封装。这意味着,根据对象的语义,对该对象进行任何操作的结果都是可以预测的。您可以通过最小化可访问状态的函数的数量来进一步改善封装。
这是避免出现全球状态的主要原因之一。全局状态可能会在没有接口表示它的情况下为对象引入依赖关系,从而使该状态对对象的用户隐藏。对具有全局依赖性的对象调用操作可能会产生变化且不可预测的结果。
最终变异的所有东西都不会操纵状态吗?
是的,但是如果它在小类的成员函数后面,而该小类是整个系统中唯一可以操纵其私有状态的实体,则该状态的范围非常狭窄。
您应该处理尽可能少的状态是什么意思?
从变量的角度来看:应尽可能少地访问几行代码。将变量的范围缩小到最小。
从代码角度来看:应该从该代码行访问尽可能少的变量。缩小代码行可能访问的变量数量(是否访问它并不重要,重要的是是否可以访问)。
全局变量之所以糟糕,是因为它们具有最大范围。即使从代码库的两行代码(从代码的POV行)访问它们,也始终可以访问全局变量。从变量的POV中,具有整个外部链接的全局变量可用于整个代码库中的每一行代码(或无论如何包括头文件的每一行代码)。尽管实际上只有2行代码可以访问,但如果全局变量对400,000行代码可见,那么当您将其设置为无效状态时,您的可疑立即列表将具有400,000个条目(也许很快减少为2条带有工具的条目,但是尽管如此,直接列表将有40万名嫌疑犯,这不是一个令人鼓舞的起点)。
同样,即使全局变量仅在整个代码库中仅被两行代码修改才有可能,但是不幸的是,代码库向后发展的趋势也倾向于使该数字急剧增加,这仅仅是因为它可以增加尽可能多的数目。急于按时完成任务的开发人员,看到了这个全局变量,并意识到他们可以通过它获得捷径。
在不纯正的语言(例如C ++)中,状态管理不是您真正在做什么吗?
在很大程度上,是的,除非您以一种非常奇特的方式使用C ++,从而使您始终处理定制的不可变数据结构和纯函数式编程-当状态管理变得复杂且复杂度高时,它通常也是大多数错误的源头通常取决于该状态的可见性/曝光度。
除了限制可变寿命之外,还有什么其他方法可以处理尽可能少的状态?
所有这些都是限制变量范围的领域,但是有很多方法可以做到这一点:
30,000*20=600,000
。如果有10个全局变量可访问,则状态复杂度可能类似于30,000*(20+10)=900,000
。一个健康的“状态复杂度”(我个人发明的度量标准)应该在几千或以下,而不是数万,绝对不要成千上万。对于免费功能,请说几百个或更少,然后我们才能开始严重维护头痛。变量之外:副作用
我上面列出的许多指导原则都是针对直接访问原始的可变状态(变量)。但是,在足够复杂的代码库中,仅缩小原始变量的范围不足以轻松推断正确性。
例如,您可能有一个中央数据结构,它位于完全SOLID抽象的接口后面,完全有能力完美地维护不变式,并且由于这种中央状态的广泛暴露而最终仍会遇到很多麻烦。不一定全局可访问但只能广泛访问的中央状态的一个示例是游戏引擎的中央场景图或Photoshop的中央层数据结构。
在这种情况下,“状态”的概念超出了原始变量的范围,而仅涉及数据结构和此类事物。同样,它有助于减小它们的范围(减少可以调用间接使它们变异的函数的行数)。
请注意,在这里我是如何故意将接口标记为红色的,因为从广泛的,缩小的体系结构级别来看,访问该接口仍然是突变状态,尽管是间接的。该类可以由于接口而保持不变,但这仅就我们推理正确性的能力而言如此。
在这种情况下,中央数据结构位于抽象接口的后面,该接口甚至可能无法全局访问。它可能只是被注入,然后(通过成员函数)从复杂代码库中的大量函数中间接地突变。
在这种情况下,即使数据结构完美地保持了自己的不变性,也可能在更广泛的层面上发生奇怪的事情(例如:音频播放器可能会维护各种不变性,例如音量水平永远不会超出0%到0%的范围)。 100%,但这不能防止用户按下播放按钮并拥有随机音频剪辑,而该音频剪辑不是他最近加载的音频剪辑,而是触发事件触发播放列表以一种有效的方式重新播放,从广泛的用户角度来看仍然不希望出现的小故障行为)。
在这些复杂的情况下保护自己的方法是“瓶颈”在代码库中可以调用最终导致外部副作用的功能的位置,即使从原始状态和接口之外的这种更广泛的系统角度来看也是如此。
看起来很奇怪,您可以看到许多地方都没有访问“状态”(以红色显示,并不表示“原始变量”,仅表示“对象”,甚至可能位于抽象接口的后面) 。每个功能都可以访问本地状态,该状态也可以由中央更新器访问,并且中央状态仅可由中央更新器访问(使其本质上不再是中央的,而是本地的)。
这仅适用于真正复杂的代码库,例如跨越1000万行代码的游戏,但是当您显着限制/瓶颈数目时,它可以极大地帮助您推理软件的正确性,并发现您的更改会产生可预测的结果。可以改变整个体系结构正常运行的关键状态的位置。
除了原始变量之外,还存在外部副作用,即使将外部副作用限制在少数成员函数中,它们也是错误的根源。如果一系列功能可以直接调用那些少数成员函数,那么系统中就有一系列功能可以间接导致外部副作用,从而增加复杂性。如果代码库中只有一个地方可以访问这些成员函数,并且一条执行路径不是由各地的偶发事件触发的,而是以一种非常可控的,可预测的方式执行的,那么它将降低复杂性。
国家复杂性
即使是国家的复杂性,也是要考虑的一个相当重要的因素。一个简单的结构,在抽象接口的后面可以广泛访问,并不是那么容易搞砸。
表示复杂体系结构的核心逻辑表示形式的复杂图形数据结构非常容易弄乱,并且甚至不会违反图形的不变式。图比简单的结构复杂很多倍,因此在这种情况下,降低代码库的感知复杂度,将访问这种图结构的位置的数量减少到绝对最小值变得尤为重要,在那种转变为拉式范式以避免零星发生的“中央更新器”策略的情况下,从各地到图数据结构的直接推送确实可以带来回报。