换个角度认识软件 林宁著 目录 为什么我们跟别人说不明白?1 用模型理解编程语言和面向对象16 理解软件背后的生意34 透过领域建模看软件的骨相53 换个角度看架构87 把团队看作分布式系统105 为什么我们跟别人说不明白? 在软件开发过程中,比较难的一件事就是如何表达需求、方案、问题,甚至有时候日常沟通也会出现“驴头不对马嘴”的沟通窘境。 之所以会出现这类表达问题,一部分原因是我们对逻辑的理解不同。大多数有经验的开发者、系统分析师都具备一定的辩证思维和方法,要说谁没有逻辑,这件事情很难说得过去。如果每个人都是用自己的思维方式和“逻辑”,让沟通过程变得非常困难。令我疑惑的是,每个人都相信逻辑是很重要的,但几乎没有文章讨论过在软件设计和开发过程中如何使用现代逻辑学,以及解决诡辩的问题。 这里讨论一些能在软件工程中使用的逻辑学基础知识,尤其是概念相关的内容在业务分析、领域建模和架构设计中都可能会被用到。 1当你提到用户时,你是指什么? 我曾经参与过一个物联网系统的设计,其中大家经常会提到一个词“设备”,但是“设备”这个词在不同的开发者眼里有不同的概念,为此,讨论“设备”这个词花费了不少的功夫,最终依然没有定义清楚。 有些开发者认为“设备”是现实中看得见摸得着的物品,另外的开发者将服务器上用于映射物理设备的实例也叫做“设备”。于是,他们在沟通时经常会出现对于“设备”的理解不一致导致的混乱。 另外一个例子是“用户”这个概念。在不同的场景(上下文)下,“用户”这个概念可以是使用软件的大活人,也可以是数据库中的一条记录,也可以是服务中的用户对象,有时候也将用户服务类叫做用户。 这样就非常混乱,不仅无法沟通,而且还导致开发者对系 统的认知也变得困难,很多东西处于混沌状态。 在咨询的工作中,我发现非常有意思的是,将软件中的概念一一定义清楚,整个系统的设计工作差不多就完成了。所以设计软件的过程和现实中人们相互交流非常类似。英国哲学家维特根斯坦把人们交流的过程叫做“语言的游戏”,当我们描述事物的时候实际上就是将有清晰边界的元素贴上标签,这个标签就是我们说的概念。 朴素的概念是来源于个人背景和理解,因此概念难以统一。正是因此不同语言之间准确地翻译也不太可能1,不同文化背景难以找到合适的概念互相映射。后来哲学家认识到人们认识概念是由一些更为基础的属性构成的,那可以认为概念就是由属性组成的。比如“人”这个概念,有四肢、直立行走的行为、皮肤光滑等属性。 这些基本的属性又是一些更基本的概念,如果我们对这些基本的概念达成共识,那么我们就有机会对概念进行统一。类似于面向对象语言Java中的类,类有各种属性,这些 1翻译是否真的可以被实现是一个经常被讨论的观点,参考:严春友.翻译之可能与不可能――对翻译问题的哲学思考. 属性最终都可以通过8种基本的数据结构描述。 因此属性是认识概念非常重要的一方面。属性包含了事物自身的性质、行为,比如黑白、高矮、是否能飞行、是否独立行走。事物除了自身的性质外,还与其他事物发生一定的关系,比如大于、相等、对称、属于等。事物的性质、行为以及和其他事物的关系,统称为事物的属性。 通过属性就能找到概念的边界。具有相同属性的概念是同一个概念,即使它们的名称不同也不应该分为不同的概念。例如,土豆和马铃薯都是同一个概念。如果意识不到属性对概念的影响,则会出现生活中的命名错误,例如小熊猫并不是小的熊猫,而是单独的一种动物。 2尝试精确定义概念 一个概念可以具有多种表达方法,对于软件设计来说,我们可以用自然语言描述概念。也可以通过定义一个类来描述,并在程序运行时实例化这个概念。通过数学或者数理逻辑,我们可以使用集合来描述一个概念。 比如“商品”这个概念,可以通过不同的方法表达。 自然语言中,商品是指可以通过货币或者其他物品交易的物品,可以是自然实体,也可以是虚拟物品。这是社会经济中对商品的描述,商品具有一个核心属性就是价格,有价格意味着可以交易。 自然语言(NaturalLanguage)就是人类讲的语言,它是人类自然发展中自然形成的,比如汉语、英语。 这类语言不是经过特别设计的,而是通过自然进化的。它的特点是语法规则只是一种规律,并非需要严格遵守的规则,这种语言含有大量的推测,以及对话者本身的认知背景(比如东西方不同的文化背景形成了大量的哩语)。认知背景赋予了词汇、概念的不同含义,比如,豆腐脑这个词,不说东西方差异,就是国内南北都会有争议。 著名的白马非马争论在于自然语言的不确定性: •从概念上说,白马这个概念不是马这个概念,所以白马非马。 •从谓词(“是”这个谓词)逻辑来说,白马这个概念代表的事物集合属于马这个概念代表的事物集合。 所以白马是马(白马属于马,但是白马这个概念不是马这个概念)。 正因为如此,才会产生大量的诡辩,让交流效率降低。 在逻辑学中,形式语言开始发挥作用。形式语言(FormalLanguage)是指用精确的数学或机器可处理的公式定义的语言。例如数学家用的数字和运算符号、化学家用的分子式等,以及编程语言中的一些符号(Token)。计算机编程也是一种形式语言,是专门用来表达计算过程的形式语言,以操作计算机。 形式语言需要严格遵守语法规则,例如1+1=2是数学中一种形式语言。 总之,知道形式语言和自然语言之间的区别,可以避免无意义的争论。软件工程师就是一个对现实业务形式化的工作岗位,将需求这种自然语言转变为代码这种形式语言。 正因为如此,需求和沟通的矛盾不可能避免,除非提出需求的人也使用形式语言,那么软件工程师的价值也就没有了。 publicclassItem[privateStringname; privateBigDecimalprice; ] 使用形式语言可以精确地定义一个概念,并使用精确的语义规则和计算机沟通,这就是软件工程师编写软件的过程。如果通过计算机语言来描述一个概念,其实就是面向对象中的一个类,这里定义商品有两个属性名称和价格: Item[name,price] 如果用集合的枚举法来表述商品就是: 计算机语言和数学语言是一种形式化的语言,可以精确地描述一个概念,但是自然语言只能通过模糊地给出概念的描述。自然语言翻译成计算机语言的不确定性,带来了无休无止的争吵,但这也是软件设计者的主要工作。 3对象应该定义多少属性? 正是因为自然语言的这种模糊性,为了更加具体地描述一个概念。哲学上概念的共识是,概念有两个基本的逻辑特征,即内涵和外延。概念反映对象的特有属性或者本质属性,同时也反映了具有这种特有属性或者本质属性的对象,因而概念有表达属性的范围。 例如商品这个概念的内涵是“能进行交换的产品”,本质属性是能进行交换,从本质上区别产品。它的外延就是投入市场能卖的所有事物。 对概念外延的清晰描述对我们设计软件产品的定位非常有帮助,我们购买软件服务无非两种情况,生活娱乐使用,或者工作使用。马克思社会经济学精妙的描述为生产资料、生活资料。这其中的逻辑完全不同,按照生活资料的逻辑设计一款生产资料的产品注定要走弯路。 概念的内涵和外延在一定条件下或者上下文中被确定的,这取决于参与人的共识。严格锁定概念的内涵和外延,能帮助我们讨论问题和改进软件模型。随意修改内涵和外延这是典型的偷换概念和诡辩。 概念的内涵和外延是一个此消彼长的兄弟。当内涵扩大时,外延就会缩小,概念就会变得越具体。当内涵缩小时,外延就会扩大,反映的事物就会越多。 这在面向对象软件建模中的影响非常明显。对象特有属性或者本质属性越少,那么这个对象能被复用的场景越多,也就是内涵越小。反之,特有属性越多,能被复用的情况就越少了。软件建模过程中随意修改概念往往意识不到,但是每一次属性的添加和移除都带来概念的内涵和外延发生变化。 非常典型的一个例子发生在订单模型中。一般来说,我们会把支付单和订单分开设计,订单的概念中没有支付这个行为,但有时候觉得支付单的存在过于复杂,会将支付单的属性放到订单中,这个时候订单的内涵和外延变了。 内涵和外延发生变化但是设计人员没有意识到,会使用同一个词语。一旦使用同一个词语就会产生二义性,二义性的存在对软件建模是致命性打击。比如用户维护的地址、地址库中的地址、订单中的地址,这三个“地址”虽然名字相同,但是内涵和外延不同。 意识不到概念的内涵和外延,是无法设计出逻辑良好的软件模型的。 4如何为变量命名? 变量命名和缓存失效是编程中最让人头疼的两件事。 变量命名实际上就是为一个概念进行定义。定义是揭示概念内涵和外延的逻辑方法,而一个准确的定义需要反映出对象的本质属性或特有属性。在下定义时,存在两个常见的困难: 1.不知道如何使用良好的逻辑方法进行定义。 2.对业务概念或者领域不熟悉。 对于第一个困难,逻辑学有一些很好的下定义方法,可以根据概念的属性、内涵和外延来进行。 属加种差定义法。这种下定义的方法通俗来说就是先把某一个概念放到另一个更广泛的概念中,逻辑学中将这个大的概念叫做“属概念”,小的概念叫做“种概念”。从这个属概念中找到一个相邻的种概念,进行比较,找出差异 化本质属性,就是“种差”。比如,对数学的定义,数学首先是一门学科,和物理学处于同类,它的本质属性是研究空间形式和数量关系。于是可以得到数学这个概念定义: 数学是一种研究现实世界的空间形式和数量关系的学科。 用这种方法给订单、支付单、物流单下一个定义: 订单是一种反映用户对商品购买行为的凭据。属概念是“凭据”,种差是“反映用户对商品购买行为”。 支付单是一种反映用户完成某一次支付行为的凭据。属概念是“凭据”,种差是“用户完成某一次支付行为”。 物流单是一种反映管理员完成某一次发货行为的凭据。属概念是“凭据”,种差是“管理员完成某一次发货行为”。 在逻辑中可以参考下面的公式: 被定义的概念=种差+属概念对于第二个痛点,这不是软件建模能解决的问题,需要充 分和领域专家讨论,获取足够的业务知识。人们对概念的定义或者认识是随着对事物的认识不断加深而变化的。一个完全对某个领域没有基本认识的软件工程师很难做出合理的软件建模,例如银行、交易所、财会等领域的软件需要大量的行业知识。 我们做消费者业务的互联网开发时,往往因为和我们的生活相关,所以这种感受并不明显。当做行业软件时,领域知识对软件模型的影响甚至是决定性的。 5避免无意义的争论 如果需要建立逻辑思维,还需要一些逻辑规律。逻辑学的三个基本规律可以让沟通更加准确,避免无意义的争论,减少逻辑矛盾,让讨论有所产出。这三个重要的规律是:同一律、矛盾律、排中律。 同一律 在同一段论述(命题和推理)中使用的概念含义不变,这 个规律就是同一律。形式化的表述是A→A。同一律描述的是在一段论述中,需要保持概念的稳定,否则会带来谬误。 在辩论赛中可以利用这个规律,赢取辩论。 比如论题是“网络会让人的生活更美好吗?”,两个主要的论点是: •网络让人们的生活更方便。 •网络让人们沉溺于虚拟世界。 假如我们选择的论点是“网络让人们的生活更方便”。在辩论赛中,我们陈述了“没有网络非常不方便”,反方被诱导描述了“打电话、写信也可以让人生活很美好,不一定需要网络,且不会像网络一样容易沉溺在虚拟世界中”。这刚好落入我们的逻辑陷阱。我们指出,邮政、电话网络也是网络的一种,对方的逻辑不攻自破。 这属于典型的“偷换概念”,我们偷换了“计算机网络”和“网络”这两个概念。 矛盾律 矛盾律应用得更为普遍,几乎所有人都能认识到矛盾律。 它的含义是,在一段论述中,互相否定的思想不能同时为真。形式化的描述是:“A不能是非A”。 矛盾律这个词的来源就是很有名的“矛和盾”的典故,出自《韩非子•难势》中。说有一个楚人卖矛和盾,牛吹得过大,说自己的盾在天底下没有矛能刺破,然后又说自己的矛,天底下的盾没有不能穿透的。前后矛盾是一个众所周知的逻辑规律,但是并不是一开始马上就能看出来