编程之道.2.20190706

前言

这是我个人总结的一些编程思想,从2014年我接触人生第一门编程语言(C)开始,它一直指引着我,我也不断的完善它。其中有些是我自己在实践中得出的结论,有些是书里学到的知识。在此分享。今后我也会一直完善它,所以你们能看到,博客标题带有编号,往后的新内容,我会直接追加在这片博客里,并更新博客标题。

对仗词

add/remove       increment/decrement  open/close
begin/end        insert/delete        show/hide
create/destroy   lock/unlock          source/target
first/last       min/max              start/stop
get/put          next/previous        up/down
get/set          old/new              locked/unlocked
opened/closed    visible/invisible    source/destination 

命名

  • 文件夹

    同函数。文件夹不使用复数形式,因为文件夹的特质已包含复数含义。

  • 函数

    首单词小写,后面的单词首字母大写

      和获取数据有关的用get。直接从数据库查询的用find。find有从数据库表里查询的含义,
      所以在一个需要隐藏数据库的层面,不应该使用find,应该使用get
    
      数据库用的是insert/delete,其他层次我们使用add/remove
    
      由于函数的参数列表也是其抽象的一部分,所以某些含义应该以参数的型式展示,而不是描
      述进参数名字中,这样的命名策略可避免出现复杂的参数名称。例如,从数据库中查询某种
      信息,findAllByxxx就可以把Byxxx省略,因为你的参数列表已经表明了这样的用意
    
  • 变量

    通常,首单词小写,后面的单词首字母大写。可以酌情使用下划线,但不要用中划线。final static常量用全大写,并用下划线分割单词。

  • 所有单词首字母大写

策略

  • 注意那些会造成知识逃逸的设计。

    • 注意那些会将接口抽象变得模糊的容器(collection与map)。
  • 程序具有金字塔结构特征,底层接口具有较宽泛的抽象,越接近顶层,接口越具象(即与业务越拟合)

    • 避免dao层出现针对特定业务设计的接口。

      判断一个接口是否针对特定业务设计,关键在于判断接口内的变化是否是由业务变化直接决定的。

      dao层的接口应该是无感情的select,insert,update,delete。一旦你发现某个接口具有业务拟合倾向,比如selectForXXX,该接口就需要重新考量。

  • 异常的区分

    防止unchecked异常发生是调用者的责任,调用者开发人员要在编码阶段保证unchecked不会在运行时出现。

    从被调用者的角度来说,一个异常应该定义为unchecked还是checked,有时不是很好区分,但可从下面几点来思考:

      1,异常是否由参数引起(是=unchecked)
      
      2,异常是否由非代码缘故引起(代码往往是对逻辑的准确表达,而非代码如网络,硬件等往往具有不准确性,不可靠性)(是=checked)
      
      3,异常的解释性有多强(强解释性的异常往往来自很具象的异常类,而弱解释性异常往往更多通过message来解释自己)(强=checked)
    

被推翻策略

  • (已被推翻,这个工具可以放心使用,这是方法内部如何实现功能的问题,它并不应该考虑类发生变化时应该怎么办,实际上,程序也不应该让类发生变化)不能使用BeanUtils.copyProperties(),或类似的工具。因为它会导致Bean接口发生变化时,副作用不可被编译器察觉。

后端架构

可遵循《阿里巴巴Java开发手册》(6.1应用分层,一章)。注意,DAO的组合复用,是在manager层做的。

关于返回值

  • 从dao返回的数据,要么是基本数据类型,要么是DO实体。

  • 从service返回的数据,要么是基本数据类型,要么是DTO实体。

DAO如何工作

每个DAO应该有一个主表,围绕这个主表产生DO,同时尽量避免联表。

《高性能Mysql》中有这样一段话:

无论如何排序都是一个成本很高的操作,所以从性能角度考虑,应尽可能避免排序或者尽可能避免对大量数据进行排序。

如果 ORDER BY 子句中的所有列来都来自关联的第一个表,那么mysql在关联处理第一个表的时候就进行文件排序。除此之外的所有情况,mysql都会先将关联的结果放到一个临时表中,然后在所有的关联都结束后,再进行文件排序。

仅供参考:我认为思考某段sql应该进入哪个DAO的时候,你需要思考这段SQL的主表是谁,而上面给出的信息,是思考这个问题的一个很不错的提示。

一些小细节

  • mapper的查询条件如果使用枚举来映射数据库表中的字段,不应该在枚举中添加数据库没有的数值,比如用某个数来表示“全部”这个概念(但其实数据库中并没有一个状态值和它对应)。如果你这么做,你会发现当你需要使用集合来配合IN查询的时候,多出来的数值会成为你的麻烦。

  • 下层为上层服务,以目标为导向。上层(业务逻辑层)需要什么,下层(数据访问层)就提供什么。而不是下层(数据访问层)有什么,上层(业务逻辑层)使用什么。

  • dao层不提供计算属性,只提供真实存在的属性。(虽然上层看不出来dao提供的属性是真实存在的还是计算出来的,但遵守这一点可以让你的sql与业务逻辑有效隔离。)

  • dao层有两个部分,一是承载实际代码的mapper.xml,一是提供接口的mapper.java。mapper.java要提供清晰明确的参数列表和返回值,禁止使用Map做参数和返回值。

  • 任何NEP问题,都由数据使用者来保证。(你要假设任何基本数据类型外的数据都有可能为NULL,并对出现NULL的情况做出处理)

  • 对数据库的增删改要聚合到特定的service接口上,数据查询则不需要。这样有助于控制数据的修改。

修行之道

  • 每个函数,每个类和每个模块都全神贯注于一事

  • 应当把类和函数做得足够小,消除对成员前戳的需要

  • 函数应该做一件事,做好这件事,只做这一件事

  • 函数要么做什么事,要么回答什么事,但二者不可得兼

  • 德墨忒尔律,模块不应了解它所操作对象的内部情形。

  • 我们喜欢把由某个公共函数调用的私有工具函数紧随在该公共函数后面,这符合了自顶向下原则,让程序读起来就像一篇报纸文章。

  • 命名正是帮助判断类的长度的一个手段,如果无法为某个类命以精确的名称,这个类大概就太长了。类名越含糊,该类越有可能拥有过多权责。

  • 你是想把工具归置到有许多抽屉,每个抽屉中装有定义和标记良好的组件的工具箱中呢,还是想要少数几个能随便把所有东西扔进去的抽屉

  • 我们希望将系统打造成在添加或修改特性时尽可能少惹麻烦的架子。在理想系统中,我们通过扩张系统而非修改现有代码来添加新特性。

  • 你是想把工具归置到有许多抽屉,每个抽屉中装有定义和标记良好的组件的工具箱中呢,还是想要少数几个能随便把所有东西扔进去的抽屉

  • 我们希望将系统打造成在添加或修改特性时尽可能少惹麻烦的架子。在理想系统中,我们通过扩张系统而非修改现有代码来添加新特性。

  • 类应该依赖于抽象而不是依赖于具体细节(面向接口编程)

  • 封装边界条件。

  • 某几个函数如果被组合调用,那么它们应该被封装成一个整体,以保持它们的完整性。

  • 对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露其数据,没有提供有意义的函数。注意,类并不简单的用取值器和赋值器将其变量推向外界,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据本体。

  • 在良好的面向对象设计中,每个对象都可以很好的完成一项任务,但是它并不试图做更多的事。

  • 将程序开发人员按照角色分为类创建者(那些创建新数据类型的程序员)和客户端程序员(那些在其应用中使用数据类型的类消费者)是大有裨益的。

  • 当继承现有类型时,也就创造了新的类型。这个新的类型不仅包括现有类型的所有成员,而且更重要的是它复制了基类的接口,也就是说,所有可以发送给基类对象的消息同时也可以发送给导出类对象。由于通过发送给类的消息的类型可知类的类型,所以这也就意味着导出类于基类具有相同的类型。

  • 把导出类看作是它的基类的过程称为向上转型。

  • 面向对象的程序设计通常简单的归纳为“向对象发送消息”。

  • 你(客户端程序员)所看到的只是有关下面两个部分内容的定义:用来表示问题空间概念的对象,以及发送给这些对象的用来表示在此空间内的行为的信息。

  • java:闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建他的作用域

    js:闭包允许你将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。

  • 我真的需要向上转型吗?(判断是否使用继承)

    继承是一种(is-a)关系,而组合是(has-a)关系

    比如,防盗门是一种(is-a)门,所以防盗门应该继承自门

    而车有一扇(has-a)门,则应该是组合关系。

  • 在设计一个系统时,目标应该是找到或创建某些类,其中每个类都有具体的用途,而且既不会太大(包含太多的功能而难以复用),也不会太小(不添加其他功能就无法使用)。

  • 将代码视作是一种有机的,进化着的生命体而去培养。

  • 类太过抽象的一个表现,是过长的参数列表(太多的属性需要对象创建者定义);一个类的合适抽象要视其所处层级而定。参数过长不一定是太过抽象,有可能是它本因在底层使用却被设计给高层调用(底层的类本来就很抽象,它们对灵活性要求较高)。

  • 类太过具象的一个表现,是过长的类名(说明该类的职责太过细节和具体)。这种情况你要考虑,它可能应该归属于某个抽象,化身为几个接口,而不是单独的一个类。

  • 如果某些类具有一些相同的特性,你该考虑把这些特性提取出来,形成一个它们的父类。

  • 恰当的原则应该是优先选择类而不是接口。从类开始,如果接口的必要性变得非常明确,那么就进行重构。(这能避免你在程序的一开始就莫名的构造出很多接口来,而且没完没了)

  • 只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那你就会触霉头了。

  • 一个接口表示:“所有实现了该接口的类看起来都像这样”。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。

  • 我们可以在任何现有类之上添加新接口,所以这意味着让方法接收接口类型,是一种让任何类都可以对该方法适配的方式。这就是使用接口的强大之处。(但是不要滥用接口)

  • 使用接口的核心原因:为了能够向上转型为多个基类型(以及由此而带来的灵活性)。

  • 当人类只发现了苹果的时候,水果这个抽象概念是没有存在价值的,只有当橘子和香蕉也被发现时,水果才具有其存在意义。(在为程序增加抽象性方面,这句话可以启发你)

  • 任何抽象性都应该是应真正的需求而产生的。当必要时,你应该首先考虑重构接口而不是到处添加额外级别的间接性,并由此带来额外的复杂性,这种额外的复杂性是非常显著的。(不要盲目添加新接口)

  • 保养代码,持续的改进代码,代码才会精进。

  • 内部类可以访问外围类的所有属性,不需要任何特殊条件。使用内部类和使用组合是不一样的。

  • 好的程序员花去90%的时间思考,研究和实验,以找出最优的方案。差的程序员花去90%的时间调试问题程序,盲目的修改程序,期望某种写法可行。

  • 对同一种事物或同一种动作的不同叫法会成为你的噩梦(别把命名不当回事)。

  • 程序必须是为了给人看而写,给机器去执行只是附带任务。

  • 封装复杂,接口简单。——《罗辑思维》

  • 此条谈论的接口是广义接口,而不是面向对象编程中的那个接口(interface)

    接口的神奇之处在于,你需要且只需要知道这样做会得到什么。

    接口的简单之处在于,它明确的告诉你它需要什么,产出什么。

  • 词语概念:接口泛指实体把自己提供给外界的一种抽象化物(可以为另一实体),用以由内部操作分离出外部沟通方法,使其能被修改内部而不影响外界其他实体与其交互的方式。

  • 在数据和数据使用者之间加上一个层次,这样做的好处是某些对数据的操作,比如控制精度,强制类型转换等可以在这个层次集中处理,而不需要每一个数据使用者都处理一遍。(这里仅讨论了数据和数据使用者,并不包括数据和数据提供者的情况)

  • 类是有层次的,最底层的类要尽可能的解耦,它们越独立就越便于使用。(类的概念越抽象,它就越灵活,我要吃水果和我要吃苹果,显然前者的选择更多)

  • 私有成员明确的告诉你,它和其他人没有关系。这能有效的降低复杂度。

  • 软件的首要技术使命——管理复杂度。封装帮助你管理复杂度的方法是不让你看到那些复杂度。抽象的主要好处就在于它使你能忽略无关的细节。

  • 按照信息隐藏的原则来思考,能够激发和促进某些设计决策的形成,反而仅按照对象原则思考却不会。

  • “这个类应该隐藏些什么?”在设计的所有层面上,都可以通过询问该隐藏些什么来促成好的设计决策。

  • 数据隐藏的关键在于,如果你不这么做,对数据的操作将会分散在程序各处,一个小小的变化可能需要修改100个地方。

  • 内聚性是用来管理复杂度的有用工具,因为当一个类的代码越集中在一个中心目标的时候,你就越容易记住这些代码的功能所在。

  • 问一个对象该对什么负责,类似于问这个对象应该隐藏些什么信息,不过这一问题能够带来更为广阔的答案,从而使这种方法具有特殊的价值。

  • 模块化的目标是使得每个子程序或者类看上去像个“黑盒子”:你知道进去什么,也知道出来什么,但是你不知道里面发生了什么。

  • 成为高效程序员的一个关键就在于,当你开发程序任一部分的代码时,都能安全地忽视程序中尽可能多的其余部分,而类就是实现这一目标的重要工具。

  • 我们对着一列火车说“加上一节车厢”,于是火车就多了一节车厢。(这就是我们所要的简单)

  • 关注类的接口所表现出来的抽象。保持类的接口所展现的抽象是一致的。

  • 系统各个组成部分的开发者都会做出一些假设,而这些假设之间的不匹配是大多数致命和难以察觉的bug的主要来源。(从自己做起,不要让自己开发的接口存在任何假设,比如某个函数必须在另一个函数之后被调用)

  • 架构应该模块化,以便在替换为新用户界面时不影响业务规则和程序的输出部分。例如,架构应该使我们很容易的做到:砍掉交互式界面的类,插入一组命令行的类。(MVC)

  • 精心设计的对象关系使关注点相互分离,从而使你能在每个时刻只专注于一件事情。

  • 一旦你能理解软件开发中任何其他技术目标都不如管理复杂度重要时,众多设计上的考虑就都变得直接了当了。

  • 当他们画复杂的对象的时候,比如说一座房屋,他们总是一层层地去画。首先画出房屋的轮廓,然后画窗和门,接下来画进一步的细节,而不是一块砖,一片瓦,一根钉的去画。——Simon(1996)

  • 每当你发现自己是通过查看类的内部实现来得知该如何使用这个类的时候,你就不是在针对接口编程了,而是在透过接口针对内部实现编程了。如果你透过接口来编程的话,封装就被破坏了,而一旦封装性开始遭到破坏,抽象能力也就快遭殃了。——《代码大全》

  • 写出有品位的代码。拜托,别让你的代码到处都是if,else,大于小于等于,加减乘除。那一点都不优雅。它们是你的内裤,必不可缺,但别人绝对不会愿意看到它们。

  • 网络请求放到类中的矛盾是,类是可以生成多个对象的,而网络请求显然应该只有一个,我们不应该造成好几个一模一样的网络请求不停的烦扰服务器的情况,它不会开心的。一旦你决定让类去发送网络请求,就会出现当你使用一个或几个对象的时候,无法得知你将发送多少个网络请求。

  • 声明一个变量之前你需要辨识这个变量的作用域,你应该从它的有效性范围来看,而不是它实际被调用的范围。

  • 没有彻底理解问题,就不要去修改代码。

  • 在没有理解代码的时候对它所做的修改越大,你对它能正确工作的信心就越低。

  • 你可能觉得你赋予了一个类操作某个数据表的职责,这就是单一职责了,但实际情况是,它可以被分解成“增删改查”四个类,它们的职责更加“单一”。愿上帝赐予你智慧,让你分辨出合适的粒度。

  • 接口并不能减少重复代码,但它能规范抽象,降低程序复杂度。

  • 当你设计程序结构时,有时会出现上层和下层行为一致的情况,这时你要多想一想,只是暂时的巧合呢,还是多余了一层。

  • 对一个已投入使用的类的任何修改都不能称为扩展,尽管你可能仅仅为它添加了一个新的接口。添加一个新的接口意味着你得全然的了解这个类是如何实现的,这样来设计扩展,不能让你的工作轻松一分。在开闭原则中所说的对扩展开放,对修改关闭,意思是你可以通过添加一些类实现新的功能,而不是通过给现有类添加新的接口。这意味着,在设计之初,你得把你的系统设计成可接纳新类的架子。

  • 代码以类为最小单位,如果你在写一个类,那么你对这个类就应该是全然明了。当你在写一个类的时候,这个类里的所有方法都不可信,因为它还在建设阶段。变量应该保持最小作用域,当出现作用域需要扩张的时候,因为你对该类全然明了,所以你应该清楚哪些变量处在扩张作名单中。

  • 存放类的文件夹不能承载类的解释功能,比如system/attachment/下的类不能在命名的时候忽略其attachment(附件)的特性,因为当这些类被提供给客户使用的时候,他是看不到文件夹的,编程中,他只能看到类名。不过类名可以用来解释方法。

  • 开闭原则,说的是,某个类投入使用后,它会在很多你不知道的地方出现,此时你对它进行的修改(对接口的修改),将会同时影响系统的许多部分。解决这种灾难的办法就是实施开闭原则,即,让系统的底层可接纳新类,由此使得对接口的修改转化为加入新的类来实现。开闭原则还意味着,任何在可预见的未来会发生修改的类或方法,都是有问题的,你应该重新设计它们,以满足开闭原则。

  • “人们有时会问我,一个函数多长才算合适?在我看来,长度不是问题,关键在于函数名和函数本体之间的语义距离”——《重构》

  • 层次的抽象是类,类的抽象是接口,接口的抽象是输入输出。(“是”替换为“源自”更准确,但“是”更简洁)所以一个接口干些什么关乎到了层次的抽象,换句话说,一个接口的职责是什么,要视其所在层次,类而定。这里的接口指的是函数的结构,而不是interface

  • 如果函数或者构造函数接收的参数会因实参不合理导致系统出错,那么此函数应该使用异常处理机制控制参数输入。

  • 接口最重要意义是定义操作规范,所以接口用来表达每一个处理单元的逻辑语义是最合适不过的。

  • 在Java语言中,一个接口可以有多个不同的实现类,从而构成一个树形的实现体系。而每一个不同的实现分支,实际上代表的是对于相同的逻辑语义的不同解读方式。

  • 写程序应该写完一个类,再写下一个类。

  • 一个类提炼的好不好,看两个方面,其一,接口是否与抽象一致;其二,对扩展的支持是否充分。这里所说的扩展,不是指去修改类,而是指类本身通过接收信息的方式而表现出不同特性的能力。

  • 可扩展性越好的类,在使用前需要越多的参数来具化。位于不同层次的类其可扩展性是不同的。底层的类需要高可扩展性来提高其灵活程度,而高层次的类则需要高具化程度降低使用成本。

  • 求同法是研究类比推理的一种方法,在求同的过程中,观察的场合越多,得出的结论越可靠。而构建一个类就是一个求同的过程。所以类的可靠性是要通过在考验中不断完善来提高的。

  • 在编写组件时,最好考虑好是否要进行复用。一次性组件间有紧密的耦合没关系,但是可复用组件应当定义一个清晰的公开接口,同时也不要对其使用的外层数据作出任何假设。

  • 来看看那些优秀的开发者一直都在坚持做的事情。 不停的练习、做实践 参加开发者大会,那里可能有一些新鲜的东西 不停地获得更多的项目经验 学习网上的课程 努力靠近牛人并和他们交流 参与开发一些好玩的,你感兴趣的项目 学习开源的框架以及类库,这也是工作必须的 尝试写博客、去分享技术、给企业做培训

  • 编程时要首先考虑类之间的协作(依赖)关系,其次才是性能。对于性能,有以下几点:1,你关注的问题不一定是系统瓶颈;2,过多的盲目优化会降低开发效率,事倍功半。

  • 函数式编程三原则:

    1,声明式编程

    2,纯函数

    3,不可变数据(较流行的immutable实现:immutable-js)

    与声明式编程相反的是指令式编程,指令式编程即告诉计算机每一步如何操作,如for循环,if-else块等,一般来说都是指令式编程。编程离不开指令式编程,但指令式编程应该位于代码底层,想一想lodash你大概就明白我的意思了。

    纯函数编程有两个特点:1,函数的输出只与输入有关,相同输入产生相同输出(函数不依赖除入参以外的外部条件);2,函数不改变除函数域以外的状态或变量。

  • 程序中,使用注释来阐述魔法数值的用途,会导致当魔法数值发生增减时,所有基于它的接口都发生抽象变化,此时你需要修改所有使用了该魔法数值的接口的注释。这是愚蠢的。你应该使用枚举来代替魔法数值,这样接口的抽象就不需要注释来维护,它由一个统一的枚举抽象来维护。有时,这个魔法数值不是某种字典,而是某种列表的id号,这种情况下也许你不应该使用枚举,望斟酌。

  • 代码的效率和复杂度成正比。大多数情况下,优化可以后做。

  • 每个Exception必须要打印到日志中一次,且仅一次(谁处理了Exception,谁打印,要注意,捕获不一定就处理,还可能将它再次抛出,这种情况下不打印)。

  • 知识逃逸,某个知识没有被有效的控制在特定的范围内,导致所有人都需要掌握该知识。知识逃逸问题是编程中一个重要又普遍存在的问题。

  • The most important purpose of creating Repository is not to exclude the persistence platform dependent logic from business logic. The most important purpose is to limit the implementation scope of business logic (Service) to the implementation of business rules. This is done by separating the operations to access business data in Repository. As an outcome of this, persistence platform dependent logic gets implemented in Repository instead of business logic (Service).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值