前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一周技术思考(第25期)-编写害羞的代码

一周技术思考(第25期)-编写害羞的代码

作者头像
王新栋
发布2021-08-20 10:29:54
2150
发布2021-08-20 10:29:54
举报
文章被收录于专栏:程序架道程序架道

大家好,这里记录,我每周读到的技术书籍、专栏、文章以及遇到的工作上的技术经历的思考,不见得都对,但开始思考总是好的。

编写害羞的代码

这两周开始,公司陆续进行校招的提前批,我也参与几场面试。现在的学生都很优秀,当然,他们的发展空间也是非常大,比如在编程这条路上还有很多关卡需要通过。

这么多年过去,我依然记得,在我第一份工作面试的时候,面试官问我,“你认为面向对象最伟大的地方在哪里”,刚走出校园的我并没有回答到面试官的“心坎里”,当时被告知的答案是多态。后来我才能够逐渐地体会到他当时这样问的初衷,因为多态能动态地改变对象的行为,有了这一“利器”才能使得很多设计原则和美妙的设计模式得以实现。

再后来,随着一边接触沟通项目需求,一边编程实现项目需求,我便更能认识到要想很“顺滑”的实行多态动作,还有一个前提,那便是一个对象要有良好的封装。

在出色的面向对象设计中,对象仅暴露必要的接口来和其他对象进行交互。除了如何使用该对象,其他细节都应当对其他对象隐藏起来。

在这几次面试沟通的过程中,我也是建议这些同学多学习一下面向对象的知识,虽然,函数式的编程语言多种多样,但是,你会发现当今最流行的软件开发语言和它们所使用的模式,都在局部地或者广泛地接受着面向对象设计与分析的“熏陶”,因为,如果我们要很好的解决复杂的业务场景,还是需要依靠面向对象的技能,尤其是在企业级应用环境中。

我最后还建议这些同学,不仅要深刻理解什么是类,还要牢固地掌握不同类型的继承关系有哪些,比如一般泛化、接口实现、抽象类继承等等,而且还要能够很明确的领会面向对象的术语,比如封装、多态等。

为什么要了解封装呢。

我把它看做是登入面向对象大门的第一个门槛,一个对象要保证不会向其它模块透露任何不必要的信息,也就是让我们的类“害羞”起来,不要随便的“袒露心扉”,要编写“害羞”的代码。如果你要改变这个对象的状态,那么请让这个对象替你来完成,因为它此刻很害羞,不想让你直接看到它的状态,害羞的更不会让你直接操作它的状态。

这样做还能让你的代码和其他代码实现隔离,更有可能保持正交性

这里,先让我们记着,编写“害羞”的代码是保持正交性的前提和基础。

那么,正交性的代码到底有怎样的魅力呢。

接下来,首先我要给你介绍下,什么是正交性(大家也可以翻看之前的文章,亦有介绍)。

这是一个从几何学中借用来的术语,如果两条直线相交后构成直角,它们就是正交的。这有什么好处呢,因为它们在各自的方向上,无论怎么用力的“折腾”都不会影响到对方。

图片
图片

图自《程序员修炼之道》

在软件领域里面,编写符合“正交性”的代码,就意味着独立和解耦。比如操作数据库相关的代码和填充用户界面数据的代码保持正交,那么,不管我们怎么更换界面都不会影响数据库,我们切换数据库也不会导致变更界面。

所以,符合正交的代码,就很容易变更和控制。

换个角度来看,编写正交的代码,能够让我们获得两个主要的收益:提高生产力和降低风险。

先看提高生产力

1、将变更限制在局部后,开发时间和测试时间都会减少。

2、正交的方法同时促进了重用。

3、组合正交组件能获得相当微妙的生产力提升。

再看降低风险

1、代码中病变的部分被隔离开。

2、对特定区域进行小的变更和修复,产生的任何问题都将局限于该区域内部。

3、正交系统可能更利于测试,因为对其进行组件设计和运行测试将更加容易。

那么,如何写出正交的代码呢。

上面大片介绍了正交性代码的好处,是不是写出这样风格的代码,要花费很大的力气呢。其实,这个还真不是一件困难的事情。

想想我们的一些编程指导性原则,你就会很快的领悟和明白应该怎么做,比如,让我们要面向抽象编程,不要依赖具体实现,让我们编写符合开闭原则的代码等等,一旦你按照这样的原则进行编程,你得到的代码就是能符合正交性的。

而我们提到的所有这些设计原则,这一切的基础都是多态。

说了这么多,也正是我为什么要建议那些同学,要深刻的理解对象的本质,要深刻的理解面向对象的封装和多态。

面向对象编程中关于对象的操作有两个,一个是对象的构建,另一个是对象的使用。比如我们创建了一个订单对象,然后在用户对象中使用了订单对象的生成订单功能,这便是一个构建,一个使用。

如果你刚开始编程,哪怕一个简单的学生信息管理的增删改查的实现,你也觉得是件很有成就的事情,当你做了多次这样的功能之后,你也就感觉很乏味。其实,这么多年下来,你会发现所有程序的实现都很相似,这里的相似是指这些程序大多数都是由极为相似的元素构成。

比如“在集合中查找某个东西”,学生集合中查找三好学生,订单集合中查找固定金额的订单,等等。我只是举了一个这样的集合的例子,还有很多比如创建、更新。你也会发现,在不同的业务场景下,他们的“元素核”都是很相似的。

面向对象设计是一种设计复杂程序的方法,它将复杂程序分解为单个的、代表特定角色与职责的类或者对象(类的实例化),其中封装功能。在软件开发的世界里,我们一直矢志不渝地管理复杂度,以“对象”的方法来思考将有助于我们定义和设计复杂的的系统,我们会把一个系统看做是一群相互作用的组件,而不是试图从整体上处理这个复杂的组合体。

你对系统的性能了解知多少

我们知道,如今在互联网应用中,分布式系统环境已经是普遍存在的运行环境,而分布式最大的不稳定性因素之一就是网络,究其根本是网络的复杂带来的性能问题。那么其实在网络之外,影响一个系统环境整体运行的性能因素还有很多,比如磁盘读取速度、一次CPU加锁的损耗、甚至是内存读取的速度有时候也要被统计到。

系统性能是对整个系统的研究,包括了所有的硬件组件和整个软件栈。所有数据路径上和软硬件上所发生的事情都包括在内,因为这些都有可能影响性能。

这也是为什么,在我们考虑一个系统的性能的时候,不会只看一个机器环境,而是要看整个大的运行环境,我们其实是很需要画一张图的,什么图呢,就是大家都各自画一张自己当前所维护的系统所在大运行环境的图,你的数据从哪里来,又到哪里去,整个的数据流动路径图。

另外,考虑一个分布式系统运行的系统性能的时候,我们还要从“全栈”来考虑,这里的全栈是指包括操作系统内核、数据库、应用程序、网络等通用系统软件栈,很显然它跟我们说的软件语言层的全栈是有区别的。

解决一个分布式系统环境下的性能问题,会是一件充满挑战的事情。往往,我们修复了一个问题,可能这个问题并没有从根本上消失,该问题极有可能被转移到其它的系统上去了,也就是问题转移了。比如你有一个web应用性能问题,然后你用Redis扛了一个大访问量的业务场景,成本实际上是被转移到Redis上去了,负责Redis的同事就要充分的设计Redis的部署结构。

解决复杂的性能问题常常需要全局性的方法。整个系统——包括自身内部和外部的交互——都可能需要被调查研究。这项工作要求有非常广泛的技能,一般不太可能集中在一人身上,这促使性能工程成为一门多变的并且充满智力挑战的工作。

刚开始,我们的研发人员接触性能问题的时候,会经常被主观因素所蒙蔽,而且这个主观是用户侧给予的。比如一个页面从点击链接开始到整个页面数据显示完成的耗时,有的用户可能认为还能接受,有的用户就认为超出了心理预期。

但是对于我们研发人员来讲,我们是不能依靠“主观”来解决问题的,我们要想办法对性能量化,也就是我们经常在系统中进行的埋点计算,比如查询数据库这个动作开始的时候记录一个时间点,返回查询数据再记录一个时间点,正如下面这张图所示的那样。这样我们便能够让我们的性能被数据感知到。所以,我们的系统中数据路径上如果还没有进行埋点记录的,当我们要量化性能的时候就会非常被动。

图自《性能之巅:洞悉系统、企业与云计算》

有的同学可能会问,我如何做到这样的埋点监控呢,我需要自己从零开发一套这样的监控系统吗。其实,在现今这么发达的开源社会中,当然不需要我们自己来开发一套这样的系统。我推荐给大家一款系统性能监控工具,它的名字叫做StatsD,这是一个用来向代码内部添加指标的工具,因此它也是一款在代码级别的分析监控应用程序,在2011年由Etsy创建,因为它的易用性和灵活性,目前已成了现代监控技术栈的主力。

我把它的开源地址列在下面,关于它的详细说明和使用,大家可以去GitHub上面查看。

https://github.com/statsd/statsd/wiki

有的同学会接着问了,我了解了性能度量的复杂性,也了解了性能监控的工具,那我应该从哪里开始监控呢,记住,尽可能地在贴近用户的地方开始监控

图片
图片

图自《监控运维实践:原则与策略》

曾经,我自己也在工作中吃过这方面的亏,我们只关心了一些Tomcat服务进程的运行情况,但一个分布式集群环境下,请求数据路径上还有更靠近用户的地方,比如负载均衡服务器。如果我们只关注Tomcat服务的监控,有些请求压根就没有过来,所以你看到的数据永远是正常的,当你在比如Nginx服务器上面部署监控的时候,你可能就会发现实际是有很多异常的错误码,这些请求都已经影响了我们的用户了。

另外,我还找了一张图,是关于我们常见的各项指标的耗时数据统计,放在了下面,大家可以自行感觉一下每个操作动作的耗时情况。

图片
图片

图自《性能之巅:洞悉系统、企业与云计算》

如何衡量一个项目需求的复杂度

如何来衡量一个项目需求的复杂度,即使不是一个项目,哪怕是一个需求提过来,你应该从哪几个方面来判断,我们的实现会有多大的复杂度呢。按照功能点吗,还是按照工期,或者是按照功能所支持的用户数呢。

在《软件工程最佳实践》这本书中,作者给我们提供了一套方法或者是方向吧,建议我们可以从算法复杂度、代码复杂度和数据复杂度,这三个方向去衡量。其实,想想也是,需求来了,最终还是要通过程序来实现的,那么程序中所包含的元素,不就是算法、数据这些元素吗。

算法复杂度

1.算法不需要计算或仅有少量的逻辑

2.算法主要由一些简单的计算及简单的逻辑运算组成

3.算法主要由简单的逻辑运算组成,但有少量中等复杂度的运算

4.算法中简单的和中等复杂度的计算和逻辑运算并存

5.算法主要由中等复杂度的计算和逻辑运算组成

6.算法中既有简单和中等复杂度的逻辑运算,也包括小部分高难度的逻辑

7.算法中高难度的逻辑多于简单的或中等复杂度的逻辑

8.算法中绝大部分都是高难度的复杂的逻辑

9.算法全部为高难度的逻辑,其中包含部分极其复杂的逻辑

10.算法中的计算和逻辑全部都极其复杂

代码复杂度

1.大多数的“编程”可以通过点击按钮和下拉列表选择来完成

2.简单的非过程代码(自动生成的、数据库、电子表格)

3.同时包含简单的和中等难度的非过程代码

4.使用项目主体框架及可复用模块来编程

5.中等难度的软件结构及简单的模块和路径

6.优秀的软件结构但包含一些复杂的模块和路径

7.代码中包含部分复杂的模块、路径及不同数据段落之间的链接

8.代码中包含较为复杂的模块、路径及不同数据段落之间的链接

9.代码中主要的路径和模块都非常庞大且复杂

10.非常复杂的代码结构并包含大型的模块和难以处理的链接

数据复杂度

1.软件应用不需要维护任何永久性的文件或数据

2.软件应用仅需要维护一个文件并仅有很少的数据交互

3.软件应用仅需要维护少量包含简单数据的文件

4.软件应用需要维护多种数据元素,但数据元素间的关联很简单

5.软件应用需要维护多个包含中等复杂度数据的文件

6.软件应用需要维护多个文件,部分文件包含复杂的数据元素及数据交互

7.软件应用需要维护多个文件,这些文件都包含复杂的数据元素及数据交互

8.软件应用需要维护多个文件,这些文件的大部分内容都是复杂的数据元素并有大量数据交互

9.软件应用需要维护多个文件,这些文件均为复杂的数据元素并有大量的数据交互

10.软件应用需要维护大量包含复杂数据元素的文件并伴有复杂的数据交互

其实,陈述下来这就是一个复杂度三角形,代码要纯手工编写,编写代码的过程中会涉及到算法和数据。那么有的同学看到这个三角形,可能就会说了,我们为了降低复杂度能否牺牲一个呢,那当然是可以的。比如,为了减轻代码的复杂度,我们可以让第一期的需求支持较少量的用户,这样数据的复杂度就会降低,进而降低整体代码的复杂度,最终降低了软件的复杂度。

图片
图片

关于代码应用软件复杂度的介绍,我们暂时结束这段描述,不过,借着这个三角形,我们可以再延伸一下,类似这样的三角关系,实际上在软件领域还有很多,比如下面这个在我们软件项目中常常被触及的一个”质量铁三角“。正如上面那个三角中的关系那样,我们可以牺牲哪一个呢,范围?资源?还是时间?,为了保证固定时间内的一定质量的项目交付,我们一般会牺牲掉范围,比如这次订单查询的导出功能,我们第一期先不上线,可以让DBA帮助线下导出数据,诸如此类的牺牲都是范围的牺牲。

图片
图片

你还能列举出哪些这样的三角关系吗。

恭喜你,又完成一次思考。

参考资料:

《性能之巅:洞悉系统、企业与云计算》、《软件开发者职业生涯指南》、《程序员修炼之道》、《面向对象的思考过程》

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序架道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档