UNIX 设计哲学:Do one thing and do it well

一次只做一件事,并做到极致。

《UNIX编程艺术》一书,提出的17条编程原则,经过时间和实践的锤炼,发展成为Unix哲学17条原则,在维基百科能搜到。

下面就来说说我对这17要原则的解读——

1、模块化原则(Rule of Modularity)

原文:开发人员应该使用定义良好的界面连接简单的部分来构建程序,所以问题是本地的,部分程序可以在未来的版本中替换以支持新的功能。此规则旨在节省调试复杂,长期且不可读的代码的时间。

解读:这条规则,现在但凡学编程的人都知道,代码要模块化,这样不仅方便别人复用,自己也能更便捷的替换新代码。而实际上,不管是学习还是实践中,模块化原则都是非常好的一条原则,比如,我们学习写作,如果能将一篇文章分模块,并通过逻辑线索串联起来,就能形成一篇不错的文章,其实就是模块化原则在起作用,我们常说的格式化写作,就是这样的。因为模块是可以替换的,模块是组成一堵墙的单元结构,可以是漂亮的空心砖,也可以是纯色的实心砖。同样,工作中也很实用,将不同的大任务分解成不同的小人物和模块,逐个击破,也是非常实用的,关键点就在于模块化是可复用和可替换的。

2、清晰原则(Rule of Clarity)

原文:开发人员应该编写清晰的程序,就好像最重要的沟通是向开发人员读取和维护程序,而不是计算机。这个规则的目的是使代码在将来的代码中尽可能易读和易理解。

解读:清晰在编程中意味着当别人看你写的代码时,能明白其中的含义,同样的,学习中也应该这样,就像我们写作就是为了梳理清楚我们的思考,表达出来让别人理解一样,看上去是在码字,实际上是在和别人沟通交流。说一些模糊和含混的话是容易的,但是要想表达出想法,清晰是非常重要的。

3、和解原则(Rule of Composition)

原文:开发人员应该编写能够与其他程序轻松通信的程序。这条规则的目的是让开发人员把项目分解成小而简单的程序,而不是过于复杂的单片程序。

解读:也叫适当妥协原则,这个原则在人际交往中应用得更多,还有就是自我思维中用得多,比如,一天我们想要锻炼身体,跑5公里,于是感性会说,算了吧,有点冷,难得换衣服了吧,被窝很舒服,理性则会说,必须坚持,为了保持健康。于是,两者开始协商,最后协商好了以后,就变成了穿保暖一点的衣服去跑步,适当降低运动量。而在与人的交流中,我们有时也会面临自己的时间和别人时间冲突的时候,这时就会需要进行适当的和解以达成共识。和解原则更像一种处世原则,让我们不能一味的强调自己,而要照顾别人的感受。

4、分离规则(Rule of Separation)

原文:开发者应该将程序的机制与程序的策略分开;一种方法是将程序分成与该接口通信的前端接口和后端引擎。这条规则旨在通过允许改变策略,尽可能降低操作机制的不稳定性来防止错误引入。

解读:这个有点不好理解,实际上后来发展出来就是java里的按照接口编程,简单说,就是A按照接口统一的协议来通信B,B提供相对应的具体功能实现,两者是分开的,互补干扰,但是对达成的共识是没有任何异议的,一旦要改变这个共识,需要重新协商并做好约束。举个例子,比如汽车的轮胎,分离规则,就是说轮胎的制造商只需要按照统一的接口生产对应尺寸的轮胎就可以了,至于在哪里生产,用什么材料生产,汽车组装时并不用关心,而和轴承对接的发动机同样也可以是多样化的。

5、简单规则(Rule of Simplicity),6、简约规则(Rule of Parsimony)

5原文:开发人员应该设计简单的方法,通过寻找方法将程序系统分解成小而直接的合作件。这条规则的目的是阻止开发者写作“复杂而美丽的复杂性”,这是现实中容易出错的程序。

6原文:开发人员应该避免编写大型程序。这一规则的目的是防止由于项目的所有者不愿抛弃显着的大量工作而导致失败或次优方法的过度投资。较小的程序不仅易于编写,优化和维护,弃用时更容易删除。

解读:这两条规则是同一个意思,如果按照现在时髦的话说,就是一切都要尽量的小,尽量的简便可执行。因为一旦没有朝着简单的方向去做,就会越来越庞大,这一点对于编程来说尤其重要,越是简单的程序,越是容易维护,也容易发现问题。而那些看上去很复杂的程序,大多数都是冗余和不必要的,而实际上,要想简单,有时需要的反而是更强大的归纳总结能力。

7、透明度原则(Rule of Transparency)

原文:开发人员应该设计可见性和可发现性,通过编写这样一种方式,他们的思维过程可以清楚地被未来的项目开发人员所看到,并使用输入和输出格式,以便识别有效输入和正确输出。此规则旨在减少调试时间并延长程序的使用寿命。

解读:这条原则容易被误解,对外部使用的人来说,只需要知道输入和输出就行了,比如计算器,按下数字进行加减乘除,只不过对于程序内部来说,透明是意味着要公开代码,这样才能更好的理解程序,方便改进程序。这条原则适用于自我提升,在反思中特别有用,比如写下了一天的工作思考,然后自己顺着写下的思路开始复盘自己一天的思考逻辑,哪些做得好,哪些做的不好。但是同样意味着,这样私密的东西,不一定都要告诉别人。

8、稳健性规则(Rule of Robustness)

原文:开发人员应该通过设计透明和可发现性来设计强大的程序,因为易于理解的代码更容易对复杂程序中无法预见的意外情况进行压力测试。此规则旨在帮助开发人员构建强大,可靠的产品。

解读:可靠性是我们一直都非常重视的,即便是移动互联网如此发达今天,我们依然会遇见,程序APP崩溃,手机卡机的情况,实际上,这也是我们常说的反脆弱性,遇见一些特定的意外情况时,我们能不能够应对和处理,就是我们平时在编写我们自己这个“程序”时最重要的事了,有的人可靠性很高,一般的小打击都是打不倒的,而有的人可靠性不那么高,一点点挫折就会奔溃。说的就是这样稳健性。

9、表示规则(Rule of Representation)

原文:开发人员在面对选择时应该选择使数据更复杂,而不是程序的逻辑,因为与复杂的逻辑相比,人类更容易理解复杂的数据。这条规则的目的是使任何开发项目的开发人员都可以使程序更易读,从而使程序得以维护。

解读:这条规则放在现在不是很适用了,因为有大数据,虽然人类擅长区分复杂的数据,但前提是数据量不是特别大,而按照今天大数据的量,还是更适合用机器去分析,有一门专业叫数据挖掘,专门干这个数据分析工作的。当然,逻辑清晰,数据详实,是很好的说明文体,也是更多增加文章的可信性的,我们现在的调查研究和综述报告就是这样的。换句话说,就是要有清晰的思路,多样的故事。

10、最小惊喜规则(Rule of Least Surprise)

原文:开发人员应该根据潜在用户的预期知识设计程序。例如,计算器程序中的“+”应该总是指“加法”。该规则旨在鼓励开发人员构建易于使用的直观产品。

解读:意味着要尽量的让每个单元有一个独立的功能,也是现在发展出来的微服务一说最早的出处了,现在因为大数据和分布式的关系,微服务越来越普及,换句话说,不仅是在编程里,即便在我们平时的生活中,也应该遵循这样的原则,在某个时间里,尽量的专心只做一件事,而不是想着要一心多用。

11、沉默的规则(Rule of Silence)

原文:开发人员应该设计程序,以免打印不必要的输出。这个规则旨在允许其他程序和开发者从程序的输出中挑出他们需要的信息,而不必分析冗长。

解读:意思本来是说,为了调试方便,程序员常常打很多日志,这样容易造成信息泄露或引起性能问题,但是,我觉得这条规则更像是简单规则的扩展,不过换个角度看,我们在思考的时候,需要适当的沉默,并不是所有的思考都要说出来,有的没有酝酿好的思考可以暂时放一放,不要急于去表达对一个观点的看法,应该尽可能多的搜集信息,再下结论。

12、修理规则(Rule of Repair)

原文:开发人员应该设计失败的程序,易于本地化和诊断,换句话说就是“失败”。这条规则旨在防止程序的错误输出成为输入,并破坏未被检测到的其他代码的输出。

解读:有错误的输入没有关系,关键是我们能不能调整并修复,就像现在很多人每天都接受很多垃圾信息一样,并没有意识到自己在接受拉结,更没有处理应对的方法,这个原则告诉我们,当我们有了可以修理的意识后,对于输入错误的输入是可以控制的,在软件测试里又叫边界测试——通过输入一些超过范围的数值或非常规操作来测试输入——这样可以验证系统的可靠性,一个软件系统是一定存在某种问题的,有问题不可怕,可怕的是不知道问题出在哪里。

13、经济规则(Rule of Economy)

原文:开发人员应该重视开发人员在机器上的时间,因为与上世纪70年代的价格相比,今天的机器周期相对便宜。这条规则旨在降低项目的开发成本。

解读:这个规则有点矛盾,一方面想要说人力成本的问题,一方面又说随着硬件价格的下降,成本的降低,我认为可以解释为,投入的成本和产出的成本,程序员的工作就是耗费时间和机器作斗争,让机器能按照人的意志而运行。付出成本是必然的,只要能在可接受的范围内就行了。

14、生成规则(Rule of Generation)

原文:开发人员应该避免手动编写代码,而是编写抽象的高级程序来生成代码。此规则旨在减少人为错误并节省时间。

解读:现在很多集成编程环境都有这样的功能,对于一些固定规则的代码,可以快速自动生成,避免手工编写程序的错误。换句话说,就是我们常说的能用自动化替代的工作就用自动化,机器比人更能做好这些工作。但不是说人工的编写就没有意义,人工的操作就是为了纠正一些可能出现的错误,并处理核心逻辑。

15、优化规则(Rule of Optimization)

原文:开发人员应该在打磨软件之前制作原型。这条规则旨在防止开发者花费太多时间来获得边际收益。

解读:现在的软件产品的制作,都会经过产品经理提出原型设计,在动手编写程序前,已经会优化很多了。这个规则特别适合思维的迭代升级过程,因为当使用这样的原则时,你会发现,自己的思考并不是完美的,而是存在很多漏洞的,但是有漏洞没有关系,慢慢找到并优化,提升,最后达到更好的效果。

16、规则的多样性(Rule of Diversity)

原文:开发者应该设计他们的程序是灵活的,开放的。这条规则的目的是使程序更加灵活,使其能够以开发者所期望的方式使用。

解读:规则的多样性,就是我们的视角更多了,能应用的武器也更多了,因为思维武器是越多越好,因为视角就会越来越多,看待问题也会越来越精确。

17、可扩展性规则(Rule of Extensibility)

原文:开发人员应该通过使其协议可扩展来设计未来,允许轻松插件,而无需修改其他开发人员的程序架构。

解读:扩展有点像多学一门技能和跨界,现在我们都提倡跨界,说的就是一个人的人生可能性,换句话说就是,人生的可扩展性很多,有的人不断学习成长,可扩展性非常大,有的人刚开始很厉害,可没有什么扩展性,只能在原有的基础上打转。

好了,17条规则说完了,字还是有点多,你能看到这里,已经很厉害了。

“风味人间”与计算机程序设计艺术

来自“风味人间”的类比

所谓美食,不过是一次又一次的相逢。我们带您穿越山海之间,偶尔的落地生根,成就万千肴变,随即化作滚滚红尘,穿越香料歧路,几度江湖夜雨后,点亮万家灯火。
《风味人间》

浮华随风去,一菜一江湖。无论置身繁华闹市,还是身居乡野陋巷,世上的滋味,就这样流转于方寸餐桌,交织在冷暖人间。我们说五味杂陈,这五味就是酸、苦、甘、辛、咸。

而我们日常食物中的食材种类可谓是丰富多彩了。简单列一下日常主要食物食性分类表:
一、寒凉类食物
1、粮油类:小米、大麦、荞麦、绿豆、薏苡仁、麻油、猪油、豆腐、黄豆芽、绿豆芽、淡豆鼓、麦芽
2、蔬菜类:芹菜、菠菜、蕹菜、苋菜、大白菜、莼菜、紫菜、甜菜、油菜、黄花菜、薇菜、生菜、丝瓜、黄瓜、冬瓜、苦瓜、竹笋、芦笋、莴苣笋、茄子、番茄、茭白、百合、荸荠、莲藕、慈姑、马兰头、马齿苋、枸杞头、白萝卜、青萝卜、菜瓜、葫芦、蘑菇、草菇、海带、葛根、鱼腥草、苤蓝
3、鱼肉类:羊肝、鸭肉、鸭血、兔肉、鸭蛋、黑鱼、田螺、螺蛳、蟹、蚌、蛤蛎、牡蛎、蛏、河蚬、青蛙、牛乳、发菜
4、水果类:梨、柑、柚子、罗汉果、柿子、杨桃、芒果、猕猴桃、香蕉、橙子、草莓、西瓜、甜瓜、胖大海、余甘子、栀子
5、其 他:食盐、白糖、蜂蜜、酱油、酱、茶、啤酒、薄荷、菊花、淡竹叶、金银花、决明子、菊苣、菰米、鲜白茅根、鲜芦根、桑叶
二、平性类食物
1、粮油类:小麦、燕麦、玉米、粳米、黄豆、黑大豆、玉米油、赤小豆、白扁豆、黑芝麻、花生、花生油、豆腐浆、豆腐皮、腐乳、大豆黄卷
2、蔬菜类:荠菜、花菜、卷心菜、蓬蒿菜、青菜、塌棵菜、西洋菜、清明菜、北瓜、马铃薯、芋艿、山药、胡萝卜、苜蓿、芡实、香菇、猴头姑、木耳、银耳、蒲公英、豌豆、蚕豆、四季豆、扁豆、荷兰豆、西兰花、甘薯、豇豆、金针菇
3、鱼肉类:猪肉、猪心、猪肝、猪脑、猪骨、火腿、牛肚、鸡肫、鹅肉、鹌鹑肉、鸽肉、鸡蛋、鹅蛋、鹌鹑蛋、鸽蛋、青鱼、鲤鱼、鲫鱼、鲈鱼、刀鱼、鲥鱼、鲟鱼、鳗鲡、白鱼、银鱼、黄鱼、鲳鱼、鳜鱼、鳐鱼、墨鱼、鲨鱼、橡皮鱼、鳖、海蜇、人乳、乌梢蛇
4、水果类:枇杷、桑椹、莲子、枣、榛子、苹果、无花果、梅子、乌梅、菠萝、甘蔗、橘仁、香榧、菱角、葵花子、西瓜子、枸杞子、枣仁、桃仁、白果、郁季仁、火麻仁、刺梨
5、其 他:味精、甘草、茯苓、菜菔子、代代花、荷叶、鸡内金
三、温热类食物
1、粮油类:高粱、籼米、糯米、牛油、菜油、豆油
2、蔬菜类:芥菜、韭菜、大头菜、南瓜、刀豆、香椿头、芫荽、辣椒、大蒜、葱、洋葱、生姜、平菇、金瓜、木瓜、魔芋
3、鱼肉类:猪肚、牛肉、牛骨髓、羊肉、羊肚、羊脑、狗肉、鸡肉、麻雀肉、雉肉、草鱼、鲢鱼、鳙鱼、鳊鱼、鳟鱼、塘鳢鱼、带鱼、黄鳝、泥鳅、蚶、河虾、海参、鲍鱼、羊乳、淡菜
4、水果类:桃子、李子、葡萄、龙眼、椰子、橄榄、杏子、杏仁、橘子、金橘、荔枝、柠檬、樱桃、杨梅、石榴、槟榔、香橼、佛手、山楂、栗子、松子、南瓜子、核桃、益智
5、其 他:红糖、桂花、桂皮、花椒、胡椒、茴香、八角茴香、丁香、砂仁、薤白、玫瑰花、玉兰花、醋、咖啡、米酒、白酒、黄酒、葡萄酒、红花、肉豆蔻、紫苏、陈皮、五香粉、高良姜、肉桂、白芷、藿香、沙棘、黄芥子

这食材,就像是程序设计中的数据结构 Number、String、Array、List、Link、Map、Tree 、Graph 等,组合成不同的抽象数据类型(ADT),你也可以理解成数据结构、模型、类、对象等范畴。

然后,这些食材怎样早就这“风味人间” (互联网软件系统、虚拟数字世界)呢?这就必须要来好好讲讲“烹饪技法”了。做菜所要的时间和调料是非常讲究的,做到位了,所做的菜的营养、色味俱全,让人吃了回味无穷,即养身体又养眼。

炒爆熘炸烹…… 让我们来看看百度百科上列出的26种烹饪技法:

1 炒
2 爆
3 熘
4 炸
5 烹
6 煎
7 贴
8 烧
9 焖
10 炖
11 蒸
12 氽
13 煮
14 烩
15 炝
16 腌
17 拌
18 烤
19 卤
20 冻
21 拔丝
22 蜜汁
23 熏
24 卷
25 滑
26 焗

厨艺世界的艺术,类比到计算机程序设计领域里面就是:

风味 = 食材 + 烹饪方法

程序 = 数据结构 + 算法

Tips:类比是思想之基。参考:《表象与本质》(Surfaces and Essences) , 作者: [美]侯世达 / [法]桑德尔。
当一个人深深地沉浸在某个活动,或被某个极不寻常的事迷住时,这强烈的兴趣可能会使大量的类比不请自来,涌入脑中。而且,是那些绝不会在其他地方产生的类比。这一现象,就像是类比的阿喀琉斯之踵,它可能会导致我们对情境产生荒诞的理解,并最终作出十分糟糕的决定。不过,在着迷的时候容易作出不同寻常的类比,这同样也是伟大灵感的源泉。准确来讲,正着迷时,发现的相似之处大部分没什么深刻的见解,但时不时的,这种由类比引来的联系,就会成为人类思维奇迹的神来之笔。实际上,当一个人对某物着迷的时候,到处都会看到有关他所迷恋事物的类比,这发生在生活的每一个角落,日夜不停。而且,每一件很微小的事都有可能对应上所迷恋事物的一部分。所以说,尽管并不常见,但有时这种不可抗拒的冲动真的会创造出非比寻常的事。
—— 引自章节:5 类比如何操纵我们

做美食,品的是风味,尝的是人间。
做程序,思的是理想,考的是现实。

程序设计是一门艺术

我们不得不接受这样一个事实:

一个未知的、无序的世界,是不可能实现“程序”的。

于是,我们抽象它,建立模型——把问题转化为结构,或者对象,或者列表,或者映射表,或者树,或者网,或者某个可以用具体规则步骤描述的事物——这就是“算法”,然后,用编程语言来表达之,或为手机APP,或为PC 网页,或为图画,或为声音,或为视频,或为3D、4D虚拟现实世界。

同样的食材,不同厨师做出来的味道,自然是千差万别。那么,同样的数据结构,不同程序员设计出来的算法和写出来的代码,实现出来的系统,自然也是不同的。

风味,不是一蹴而就,需要时间与火候;画作,不是寥寥几笔,需要不断揣摩线条、色彩、构图、光线。在追求探索美味与美色的道路上,食艺家与画艺家需要达到禅的境界,在仿佛时间静止的流动中,付出全情之投入与专注。

同样的,一个美妙的系统,代码,架构——我们这些程序艺家门,也需要臻于至善之境——禅。

编程是一门艺术——我们不得不承认。这里面倾注了人类高度的创造性与智慧。

代码欣赏: KMP 算法源代码

/*** getNext (pattern) 函数: 计算字符串 pattern 的最大公共前后缀的长度 (max common prefix suffix length)*/
fun getNext(pattern: String): IntArray {val n = pattern.lengthval next = IntArray(n, { -1 })var j = 0next[0] = j(1 until n).forEach {val i = itwhile (j > 0 && pattern[i] != pattern[j]) {j = next[j - 1]}if (pattern[i] == pattern[j]) {j++}next[i] = j}return next
}/*** kmp substring search algorithm* @param text : the source text* @param pattern : the search pattern*/
fun kmp(text: String, pattern: String): Int {val m = pattern.lengthval n = text.lengthif (pattern.isEmpty()) {return 0}// j: the current index of patternvar j = 0val next = getNext(pattern)(0..n - 1).forEach {// i: the current index of textval i = itwhile (j > 0 && text[i] != pattern[j]) {j = next[j - 1]}if (text[i] == pattern[j]) {j++}if (j == m) {return i - m + 1}}return -1
}fun main() {var text = "addaabbcaabffffggghhddabcdaaabbbaab"var pattern = "aabbcaab"print("${getNext(pattern).joinToString { it.toString() }} \n")var index = kmp(text, pattern)println("$pattern is the substring of $text, the index is: $index")text = "hello"pattern = "ll"print("${getNext(pattern).joinToString { it.toString() }} \n")index = kmp(text, pattern)println("$pattern is the substring of $text, the index is: $index")text = "abbbbbbcccddddaabaacabdcddaabbbbaad"pattern = "aabaacab"print("${getNext(pattern).joinToString { it.toString() }} \n")index = kmp(text, pattern)println("$pattern is the substring of $text, the index is: $index")}// 输出:
//0, 1, 0, 0, 0, 1, 2, 3
//aabbcaab is the substring of addaabbcaabffffggghhddabcdaaabbbaab, the index is: 3
//0, 1
//ll is the substring of hello, the index is: 2
//0, 1, 0, 1, 2, 0, 1, 0
//aabaacab is the substring of abbbbbbcccddddaabaacabdcddaabbbbaad, the index is: 14

编程艺术:本质、精神、模型、原则等

《禅与计算机程序设计艺术》 / 陈光剑

软件开发的本质

从本质上来说,软件开发过程就是问题空间到解决方案空间的一个映射转化。

“问题空间”就是系统要解决的“领域”问题。因此,也可以简单理解为一个领域就对应一个问题空间,是一个特定范围边界内的业务需求的总和。

“领域模型”就是“解决方案空间”,是针对特定领域里的关键事物及其关系的可视化表现,是为了准确定义需要解决问题而构造的抽象模型,是业务功能场景在软件系统里的映射转化,其目标是为软件系统的构建统一的认知。

每个软件系统本质上都解决了特定的问题,属于某一个特定领域,实现了同样的核心业务功能来解决该领域中核心的业务需求。领域模型在软件开发中的主要起到如下作用。

  • 帮助分析理解复杂业务领域问题,描述业务中涉及的实体及其相互之间的关系,是需求分析的产物,与问题域相关。

  • 是需求分析人员与用户交流的有力工具,是彼此交流的语言。

  • 分析如何满足系统功能性需求,指导项目后续的系统设计。

代码艺术家

“根本没有艺术这种东西,唯有艺术家而已”。(贡布里希《艺术的故事》)

这句话的意思是,艺术其实就是艺术家的一种表达方式、情绪和价值观。艺术家在哪里,艺术就在哪里;艺术在哪里,美就在哪里。

禅是什么?计算机程序设计艺术又是什么?

我想说,这个程序设计的艺术也是不存在的,只有程序员而已——我们不妨称之为“代码艺术家”好了。程序员在哪里,程序设计的艺术就在哪里,人类的智慧跟创造力就在哪里。

软件研发是技术也是艺术,不仅要有一行行的代码,还要有主题思想、想象力、宏观架构。

我有一个梦想,我写的代码,可以像诗歌一样优美。我有一个梦想,我做的设计,能恰到好处,既不过度,也无不足。

软件的复杂性

然而,现实却是——

“不管你们有多敬业,加多少班,在面对烂系统时,你仍然会寸步难行,因为你大部分的精力不是在应对开发需求,而是在应对混乱。”(Robert C.Martin)

“软件的复杂性是一个基本特征,而不是偶然如此。”(Frederick P.Brooks.Jr《人月神话》 )

问题域有其复杂性,而软件在实现过程中又有很大的灵活性和抽象性,导致软件具有天然的复杂性。缺少技艺。“写代码”作为一种技能,入门并不是很难。但是要像高手那样优雅地“写好代码”并不是一件容易的事,需要持续地学习和实践。

匠人精神

把简单的事情做到极致,功到自然成,最终“止于至善”。
秋山利辉《匠人精神》

软件设计不仅是“技术”(Technique),更是一门“技艺”(Craftsmanship)。要想控制复杂度,防止系统腐化,我们不能只满足做一个搬砖的“码农”,而是要坚持自己的技术梦想和技术信仰。

怀有一颗“匠人”之心,保持专注、持续学习,每天进步一点点。唯有如此,我们才有可能“从码农走向工匠”!

代码自律精神

破窗效应(Broken Windows Theory)是犯罪心理学中一个著名的理论,由JamesQ. Wilson和George L. Kelling提出,刊于The Atlantic Monthly 1982年3月版中一篇题为“Broken Windows”的文章。此理论认为:环境中的不良现象如果被放任存在,就会诱使人们仿效,甚至变本加厉。以一幢有少许破窗的建筑为例,如果破窗不被修理好,可能将会有破坏者破坏更多的窗户。最终,他们甚至会闯入建筑内,如果发现无人居住,也许就在那里定居或者纵火。一面墙,如果出现一些涂鸦而没有被清洗掉,那么很快,墙上就布满了乱七八糟、不堪入目的东西;一条人行道有些许纸屑,不久后就会有更多垃圾,最终人们会视若理所当然地将垃圾顺手丢弃在地上。这个现象,就是犯罪心理学中的“破窗效应”。

“第一扇破窗”常常是事情恶化的起点。

从“破窗效应”中我们可以得到这样一个道理:任何一种已存在的不良现象都在传递着一种信息,会导致不良现象无限扩展,同时必须高度警觉那些看起来是偶然的、个别的、轻微的“过错”,如果对“过错”不闻不问、熟视无睹、反应迟钝或纠正不力,就会纵容更多的人“去打烂更多的窗户”,极有可能演变成“千里之堤,溃于蚁穴”的恶果。

在软件工程中,“破窗效应”可谓是屡见不鲜。面对一个混乱的系统和一段杂乱无章的代码,后来人往往会加入更多的垃圾代码。这也凸显了规范和重构的价值。首先,我们要有一套规范,并尽量遵守规范,不要做“打破第一扇窗”的人;其次,发现有“破窗”,要及时地修复,不要让事情进一步恶化。整洁的代码需要每个人的精心呵护,需要整个团队都具备一些工匠精神。

KISS 哲学

KISS:Keep It Simple and Stupid。Less Is More。一个方法只做一件事情。单一职责原则(SingleResponsibility Principle,SRP)。

Robert C. Martin有一个信条:函数的第一规则是要短小,第二规则是要更短小。

真正的“简单”绝不是毫无设计感,上来就写代码,而是“宝剑锋从磨砺出”,亮剑的时候犹如一道华丽的闪电,背后却有着大量的艰辛和积累。真正的简单,不是不思考,而是先发散、再收敛。在纷繁复杂中,把握问题的核心。

Less Is More:维护过遗留系统、受过超长函数折磨的读者应该深有体会,相比于3000行代码的“庞然大物”,肯定是更短小的函数更易于理解和维护。有时保持代码的逻辑不变,只是把长方法改成多个短方法,代码的可读性就能提高很多。超长方法是典型的代码“坏味道”,对超长方法的结构化分解是提升代码可读性最有效的方式之一。

“我不是一个伟大的程序员,只是习惯比较好而已。”(Kent Beck)

只有养成精益求精、追求卓越的习惯,才能保持精进,写出好的代码。

SOLID :5大设计原则

5个原则分别如下:

  • Single Responsibility Principle(SRP):单一职责原则。

  • Open Close Principle(OCP):开闭原则。

  • Liskov Substitution Principle(LSP):里氏替换原则。

  • Interface Segregation Principle(ISP):接口隔离原则。

  • Dependency Inversion Principle(DIP):依赖倒置原则。
    SOLID 设计原则最早由Robert C. Martin在2000年的论文“Design Principles and DesignPatterns”中引入。 SOLID 是开头字母的缩写,其本身就有“稳定的”的意思,寓意是“遵从SOLID原则可以建立稳定、灵活、健壮的系统”。

设计模式

With patterns, you can use the solution a million times over, without everdoing it the same way twice.
利用模式,我们可以让一个解决方案重复使用,而不是重复造轮子。
克里斯托佛·亚历山大《建筑的永恒之道》。Christopher Alexander, 是当代建筑大师。在对建筑理论的探索过程当中,亚历山大形成了自己的有关建筑、自然和生活的哲学。

视觉思维:想象力与抽象思维

如果你能闭上眼睛,让算法在你的眼前舞动——这就是视觉思维。如果,你能牛逼到,能看到01字节的跳动(ByteDance),那你就是编程界的“天神”般的存在了。

“人类之所以成为人类,是因为人类能够想象”。(赫拉利《人类简史》)

“想象力比知识更重要,因为知识是有限的,而想象力概括着世界的一切,推动着进步,并且是知识进化的源泉。”(爱因斯坦《论科学》)

抽象思维以词为中介来反映现实。这是思维的最本质特征,也是人的思维和动物心理的根本区别。

正是抽象思维帮助人类从具体事物中抽象出各种概念,再用这些概念去构筑种种虚构的故事。这些概念包括经济(例如货币、证券)、文学、艺术和科学等,都是建立在抽象的基础之上的。

“所有事物都要经过两次创造的原则,第一次为心智上的创造,第二次为实际的创造”。(柯维《高效能人士的七个习惯》)

先想清楚目标,然后努力实现。不管是人生大问题,还是阶段性要完成的事情,都需要目标清晰、有的放矢。

平衡的艺术:Rule of Three

Rule of Three 也被称为“三次原则”,是指当某个功能第三次出现时,就有必要进行“抽象化”了。这也是软件大师Martin Fowler在《重构》一书中提出的思想。三次原则指导我们可以通过以下步骤来写代码。(1)第一次用到某个功能时,写一个特定的解决方法。(2)第二次又用到的时候,复制上一次的代码。(3)第三次出现的时候,才着手“抽象化”,写出通用的解决方法。这3个步骤是对DRY原则和YAGNI原则的折中,是代码冗余和开发成本的平衡点。同时也提醒我们反思,是否做了很多无用的超前设计、代码是否开始出现冗余、是否要重新设计。软件设计本身就是一个平衡的艺术,我们既反对过度设计(OverDesign),也绝对不赞成无设计(No Design)。

参考资料

爱因斯坦《爱因斯坦文集》
Frederick P.Brooks.Jr《人月神话》
克里斯托佛·亚历山大《建筑的永恒之道》
赫拉利《人类简史》
秋山利辉《匠人精神》

7 大程序设计原则

编程的工作既是技术活,也是体力活,而编写优秀的软件,更是一件比较难的事情。

初级程序员只希望代码不出错,顶级程序员却把写代码当成艺术,当年雷军以过人的能力成为求伯君的左膀右臂,其早年的代码被说成“像诗一样优美”。

很多大牛,在着手写代码时并不是直接上手编写,而是根据需求进行设计,不但将代码中 Bug 出现的机率降到最低,还让代码具有高可读性,高安全性等等。

那大牛们都遵循怎样的原则呢,我们能不能学习一下?

将大牛们的经验总结到一起,可以得到以下「7 大程序设计原则」 。这些设计原理源于对实际软件开发现场的分析,是提高代码质量的经验结晶。

让我们一起一探究竟吧!

01 简单性原则

Simplicity Principle

What:追求简单
简单性原则就是追求简单。

说得极端一点,就是自始至终都以最简单的逻辑编写代码,让编程初学者一眼就能看懂。

因此,在编程时我们要重视的是局部的完整性,而不是复杂的整体关联性。

Why:Bug 喜欢出现在复杂的地方
软件故障常集中在某一个区域,而这些区域都有一个共同的特点,那就是复杂。编写代码时如果追求简单易懂,代码就很难出现问题。

不过,简单易懂的代码往往给人一种不够专业的感觉。这也是经验老到的程序员喜欢写老练高深的代码的原因。所以我们要有足够的定力来抵挡这种诱惑。

Do:编写自然的代码
努力写出自然的代码。放下高超的技巧,坚持用简单的逻辑编写代码。

既然故障集中在代码复杂的区域,那我们只要让代码简单到让故障无处可藏即可。不要盲目地让代码复杂化、臃肿化,要保证代码简洁。

02 同构原则

Isomorphism Principle

What:力求规范
同构原则就是力求规范。

同等对待相同的东西,坚持不搞特殊。同等对待,举例来说就 是同一个模块管理的数值全部采用同一单位、公有函数的参数个数统一等。

Why:不同的东西会更显眼
相同的东西用相同的形式表现能够使不同的东西更加突出。不同的 东西往往容易产生 bug。遵循同构原则能让我们更容易嗅出代码的异样, 从而找出问题所在。

图表和工业制品在设计上追求平衡之美,在这一点上,同构原则也 有着相似之处。统一的代码颇具美感,而美的东西一般更容易让人接 受,因此统一的代码有较高的可读性。

Do:编写符合规范的代码
我们要让代码符合一定的规范。不过,这会与程序员的自我表现欲相冲突。

为了展现自己的实力,有些程序员会无视编程规范,编写独特的代码。可靠与简单是代码不可或缺的性质,但这些程序员常常在无意间让代码变得复杂。

这就把智慧与个性用错了地方。小小的自我满足远不及代码质量重要。所以在编写代码时,务必克制住自己的表现欲,以规范为先。

03 对称原则

Symmetry Principle

What:讲究形式上的对称
讲究形式上的对称。

对称原则就是讲究形式上的对称,比如有上就有下,有左就有右, 有主动就有被动。

也就是说,我们在思考一个处理时,也要想到与之成对的处理。比 如有给标志位置 1 的处理,就要有给标志位置 0 的处理。

Why:帮助读代码的人推测后面的代码
具有对称性的代码能够帮助读代码的人推测后面的代码,提高其理解代码的速度。同时,对称性会给代码带来美感,这同样有助于他人理解代码。

此外,设计代码时将对称性纳入考虑的范围能防止我们在思考问题时出现遗漏。如果说代码的条件分支是故障的温床,那么对称性就是思考的框架,能有效阻止条件遗漏。

Do:编写有对称性的代码
在出现“条件”的时候,我们要注意它的“反条件”。每个控制条件都存在与之成对的反条件(与指示条件相反的条件)。要注意条件与反条件的统一,保证控制条件具有统一性。

我们还要考虑到例外情况并极力避免其发生。例外情况的特殊性会破坏对称性,成为故障的温床。特殊情况过多意味着需求没有得到整理。此时应重新审视需求,尽量从代码中剔除例外情况。

命名也要讲究对称性。命名时建议使用 set/get、start/stop、begin/ end 和 push/pop 等成对的词语。

04 层次原则

Hierarchy Principle

What:讲究层次
注意事物的主从关系、前后关系和本末关系等层次关系,整理事物的关联性。

不同层次各司其职,同种处理不跨越多个层次,这一点非常重要。比如执行了获取资源的处理,那么释放资源的处理就要在相同的层次进行。又比如互斥控制的标志位置 1 和置 0 的处理要在同一层次进行。

Why:层次结构有助于提高代码的可读性
有明确层次结构的代码能帮助读代码的人抽象理解代码的整体结构。读代码的人可以根据自身需要阅读下一层次的代码,掌握更加详细的信息。

这样一来就可以提高代码的可读性,帮助程序员表达编码意图,降低 bug 发生的概率。

Do:编写有抽象层次结构的代码
在编写代码时设计各部分的抽象程度,构建层次结构。保证同一个层次中的所有代码抽象程度相同。另外,高层次的代码要通过外部视角描述低层次的代码。这样做能让调用低层次代码的高层次代码更加简单易懂。

05 线性原则

Linearity Principle

What:处理流程尽量走直线
线性原则就是让处理流程尽量走直线。

一个功能如果可以通过多个功能的线性结合来实现,那它的结构就会非常简单。

反过来,用条件分支控制代码、毫无章法地增加状态数等行为会让代码变得难以理解。我们要避免做出这些行为,提高代码的可读性。

Why:直线处理可提高代码的可读性
复杂的处理流程是故障的温床。

故障多出现在复杂的条件语句和循环语句中。另外,goto 等让流程出现跳跃的语句也是故障的多发地。

如果能让处理由高层次流向低层次,一气呵成,代码的可读性就会大幅提高。与此同时,可维护性也将提高,添加功能等改良工作将变得更加容易。

一般来说,自上而下的处理流程简单明快,易于理解。我们应避开复杂反复的处理流程。

Do:尽量不在代码中使用条件分支
尽量减少条件分支的数量,编写能让代码阅读者线性地看完整个处理流程的代码。

为此,我们需要把一些特殊的处理拿到主处理之外。保证处理的统一性,注意处理的流程。记得时不时俯瞰代码整体,检查代码是否存在过于复杂的部分。

另外,对于经过长期维护而变得过于复杂的部分,我们可以考虑对其进行重构。明确且可靠的设计不仅对我们自身有益,还可以给负责维护的人带来方便。

06 清晰原则

Clarity Principle

What:注意逻辑的清晰性
清晰原则就是注意逻辑的清晰性。

逻辑具有清晰性就代表逻辑能清楚证明自身的正确性。也就是说,我们编写的代码要让人一眼就能判断出没有问题。任何不明确的部分都 要附有说明。

保证逻辑的清晰性要“不择手段”。在无法用代码证明逻辑正确性的情况下,我们也可以通过写注释、附文档或画图等方法来证明。不过,证明逻辑的正确性是一件麻烦的事,时间一长,人们就会懒得用辅助手段去证明,转而编写逻辑清晰的代码了。

Why:消除不确定性
代码免不了被人一遍又一遍地阅读,所以代码必须保持较高的可读性。编写代码时如果追求高可读性,我们就不会采用取巧的方式编写代码,编写出的代码会非常自然。

采用取巧的方式编写的代码除了能让计算机运行以外没有任何意义。代码是给人看的,也是由人来修改的,所以我们必须以人为对象来编写代码。

消除代码的不确定性是对自己的作品负责,这么做也可以为后续负责维护的人提供方便。

Do:编写逻辑清晰的代码
我们要编写逻辑清晰的代码。

为此,我们应选用直观易懂的逻辑。会给读代码的人带来疑问的部分要么消除,要么加以注释。

另外,我们应使用任何人都能立刻理解且不存在歧义的术语。要特别注意变量名等一定不能没有意义。

07 安全原则

Safty Principle

What:注意安全性
安全原则就是注意安全性,采用相对安全的方法来对具有不确定性的、模糊的部分进行设计和编程。

说得具体一点,就是在编写代码时刻意将不可能的条件考虑进去。比如即便某个 i f 语句一定成立,我们也要考虑 else 语句的情况;即便某个 case 语句一定成立,我们也要考虑 default 语句的情况;即便某个变量不可能为空,我们也要检查该变量是否为 NULL。

Why:防止故障发展成重大事故
硬件提供的服务必须保证安全,软件也一样。

硬件方面,比如取暖器,为防止倾倒起火,取暖器一般会配有倾倒自动断电装置。同样,设计软件时也需要考虑各种情况,保证软件在各种情况下都能安全地运行。这一做法在持续运营服务和防止数据损坏等方面有着积极的意义。

Do:编写安全的代码
选择相对安全的方法对具有不确定性的部分进行设计。列出所有可能的运行情况,确保软件在每种情况下都能安全运行。理解需求和功能,将各种情况正确分解到代码中,这样能有效提高软件安全运行的概率。

为此,我们也要将不可能的条件视为考察对象,对其进行设计和编程。不过,为了统一标准,我们在编写代码前最好规定哪些条件需要写,哪些条件不需要写。

摘自:《编程的原则:程序员改善代码质量的 101 个方法》
作者:[日]上田勋

使用 SpringBoot 整合 MyBatis 开发 开启驼峰映射功能

使用 SpringBoot 整合 MyBatis 开发时,发现从数据库中查询到的结果封装到javabean中,只要表中有下划线的字段,就会出现null值

MyBatis默认是属性名和数据库字段名一一对应的,即

数据库表列:user_name

实体类属性:user_name

但是java中一般使用驼峰命名

数据库表列:user_name

实体类属性:userName

例如,在写注解式的Mapper代码时:

@Select("select * from kunlun_result where id=#{resultId} limit 1")

KunlunResultWithBLOBs findById(Long resultId);

上面的KunlunResultWithBLOBs对象的值要想正常映射上, 就需要开启驼峰映射功能.

在SpringBoot中,可以通过设置map-underscore-to-camel-case属性为true来开启驼峰功能:

mybatis:

mapper-locations: classpath:mapper/*.xml#注意:一定要对应mapper映射xml文件的所在路径

configuration:

map-underscore-to-camel-case: true  # 开启驼峰命名

Linux 展示 IP 命令行

ip addr show dev eth0

Display information about a single network interface

To get information about a specific network interface, use ip addr show dev followed by the device name. For example, to query eth0, you would type:

ip addr show dev eth0

https://linuxize.com/post/linux-ip-command/

json 泛型对象的序列化 parseObject by TypeReference

public static T parseObject(String text,TypeReference type,Feature... features) {

return parseObject(text, type.type,ParserConfig.global,DEFAULT_PARSER_FEATURE, features);

}

用法示例:

TypeReference>>MAP_TYPE_REFERENCE =new TypeReference>>(){};

JSON.parseObject(text,MAP_TYPE_REFERENCE);

如何在kotlin中使用$字符串而不是转义符

如果你需要在原始字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:

例如,代码里需要使用字符串:  $9.99,在Kotlin中就有点恶心了,得这么写前面的$符号得这么写:  ${'$'}

val price = """

${'$'}9.99

"""

Spring 单例 Bean 与多线程深度分析

Spring 的 bean默认是单例的,在高并发下,如果在 Spring 的单例 bean 中设置成员变量,则会发生并发问题。最近在进行开发时,错误的在单例的bean中使用了成员变量,导致多个线程大并发访问时,出现赋值错误及日志打印混乱的问题。

本文就对单例 bean 及多线程安全的问题做一次较为深入的探讨,也是对自我的一次反省,之后的开发中,杜绝此类问题,修正开发习惯。

单例模式

首先我们回顾一下单例模式的概念。单例模式的意思是只有一个实例,例如在Spring容器中某一个类只有一个实例,而且自行实例化后并项整个系统提供这个实例,这个类称为单例类。

当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对单例状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。

一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如 RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder 等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

那么如何提升bean的线程安全呢?

简单的说,若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;

若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

更进一步划分:

常量始终是线程安全的,因为只存在读操作。

每次调用方法前都新建一个实例是线程安全的,因为没有访问共享的资源。

局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量和方法内变量。这也就是我们常说的方法封闭。

如果实例无状态,则是线程安全的。如果实例中存在对同一个值的不同的操作行为,或者值在不同线程中都会变,那么就需要注意,不要使用成员变量存储属性。

这里我们引入无状态bean和有状态bean。

有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。

无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。

在spring中无状态的Bean适合用不变模式,就是单例模式,这样可以共享实例提高性能。

有状态的Bean在多线程环境下不安全,适合用 Prototype 原型模式。

Prototype: 每次对 bean 的请求都会创建一个新的 bean 实例。

Servlet是单例多线程

struts2每次处理一个请求,struts2就会实例化一个对象,这样就不会有线程安全的问题了。Struts2 是线程安全的,当然前提情况是,Action 不交给 spring管理,并且不设置为单例。

Spring mvc 线程不安全的原因

请求时多线程请求的,但是每次请求过来调用的Controller对象都是一个,而不是一个请求过来就创建一个controller对象

原因就在于如果这个controller对象是单例的,那么如果不小心在类中定义了类变量,那么这个类变量是被所有请求共享的,

这可能会造成多个请求修改该变量的值,出现与预期结果不符合的异常

在单例的情况下 相当于所有类变量对于每次请求都是共享的,每一次请求对类变量的修改都是有效的

那有没有办法让controller不以单例而以每次请求都重新创建的形式存在呢?答案是当然可以,只需要在类上添加注解@Scope("prototype")即可,这样每次请求调用的类都是重新生成的(每次生成会影响效率)还有其他方法么?

答案是肯定的!使用ThreadLocal 来保存类变量,将类变量保存在线程的变量域中,让不同的请求隔离开来.

注:servlet Struts1 SpringMvc 是线程不安全的,当然如果你不使用实例变量也就不存在线程安全的问题了。

总结

为避免发生线程安全问题,在开发和设计系统的时候注意下一下三点:

自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明

对线程环境下,对每一个共享的可变变量都要注意其线程安全性

我们的类和方法在做设计的时候,要尽量设计成无状态的

怎样实现线程安全的成员变量?

@Service
public class SpecificationParserClickHouse implements SpecificationParser {@ResourceFieldFeatureBuilder fieldFeatureBuilder;@ResourceKunlunSqlHelper kunlunSqlHelper;/*** 线程安全的表达式对象*/ThreadLocal<IQuerySpecification> spec =new ThreadLocal<>();
ThreadLocal<List<IQuerySpecification>> specList =
new ThreadLocal<>();public synchronizedThreadLocal<List<IQuerySpecification>> getSpecList() {
if (specList.get() == null) {specList.set(new ArrayList<>());}return specList;
}
...
}

jackson 序列化忽略未知字段: How to Ignore Unknown Properties While Parsing JSON in Java

One of the common problem while parsing JSON in Java using Jackson API is that it fails when your JSON contains unknown properties i.e. your Java class doesn't have all the field corresponding to all JSON properties.

使用 Jackson API 在Java中解析JSON时的一个常见问题是,当JSON包含未知属性时,即Java类没有与所有JSON属性对应的所有字段时,解析失败。

Anyway, it was our fault that we didn't review code properly and allowed him to release his code into production without handling unknown files. The issue could have simply been avoided if he was familiar with Jackson library in a little bit more detail.

Jackson API provides two ways to ignore unknown fields, first at the class level using @JsonIgnoreProperties annotation and second at the ObjectMapper level using configure() method.

You will see both approaches in this article and learn how to use them and when to use @JsonIgnoreProperties and when to ignore unknown fields in JSON globally at the ObjectMapper level.

不管怎样,这是我们的错,我们没有正确地审查代码,并允许他在不处理未知文件的情况下将代码发布到生产环境中。如果他对Jackson library 更为熟悉一点,这个问题本可以简单地避免。

jackson api提 供了两种忽略未知字段的方法:

第一种是在类级别使用 @JsonIgnoreProperties 注解,

第二种是在 ObjectMapper 级别使用configure() 方法。

Ignoring unknown properties using @JsonIgnoreProperties

If you are creating a Model class to represent the JSON in Java, then you can annotate the class with @JsonIgnoreProperties(ignoreUnknown = true) to ignore any unknown field. Which means if there is a new field is added tomorrow on JSON which represent your Model then Jackson will not throw UnrecognizedPropertyException while parsing JSON in Java.

如果您正在创建一个模型类来用Java表示JSON,那么您可以用 @JsonIgnoreProperties(ignoreUnknown=true)注释该类以忽略任何未知字段。这意味着如果明天在JSON上添加了一个新的字段来表示您的模型,那么Jackson在Java中解析JSON时不会抛出UnrecognizedPropertyException。

using @JsonIgnoreProperties annotation as shown below:

Add the following to the top of your class (not to individual methods):

@JsonIgnoreProperties(ignoreUnknown = true)

public class Foo {

...

}

Depending on the jackson version you are using you would have to use a different import in the current version it is:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

in older versions it has been:

import org.codehaus.jackson.annotate.JsonIgnoreProperties;

Ignoring Unknown Property in JSON Globally using Jackson

Another way to deal with unknown properties in JSON you are parsing is to configure ObjectMapper not to fail when it encounters an unknown property. This will also solve the problem of UnrecognizedPropertyException. You can enable this setting by calling configure() method as shown below:

import com.fasterxml.jackson.databind.DeserializationFeature;

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper objectMapper = new ObjectMapper();

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

This will now ignore unknown properties for any JSON it's going to parse, You should only use this option if you can't annotate a class with @JsonIgnoreProperties annotation.

In addition to 2 mechanisms already mentioned, there is also global feature that can be used to suppress all failures caused by unknown (unmapped) properties:

// jackson 1.9 and before

objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

// or jackson 2.0

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

This is the default used in absence of annotations, and can be convenient fallback.

一个完整使用 jackson 的 JsonUtil 的工具类示例

package com.bytedance.kunlun.util;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.DeserializationFeature;

import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import java.util.List;

import java.util.Map;

@Slf4j

public class JsonUtil {

public static final ObjectMapper mapper =new ObjectMapper();

static {

// 忽略未知字段

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

}

public static String serialize(Object obj) {

if (obj ==null) {

return null;

}

if (obj.getClass() ==String.class) {

return (String) obj;

}

try {

return mapper.writeValueAsString(obj);

}catch (JsonProcessingException e) {

log.error("json序列化出错:" + obj, e);

return null;

}

}

public static T parse(String json,Class tClass) {

try {

return mapper.readValue(json, tClass);

}catch (IOException e) {

log.error("json解析出错:" + json, e);

return null;

}

}

public static ListparseList(String json,Class eClass) {

try {

return mapper.readValue(json,mapper.getTypeFactory().constructCollectionType(List.class, eClass));

}catch (IOException e) {

log.error("json解析出错:" + json, e);

return null;

}

}

public static MapparseMap(String json,Class kClass,Class vClass) {

try {

return mapper.readValue(json,mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));

}catch (IOException e) {

log.error("json解析出错:" + json, e);

return null;

}

}

}

参考资料

Ignoring new fields on JSON objects using Jackson [duplicate]

https://www.baeldung.com/jackson-ignore-properties-on-serialization

https://www.baeldung.com/jackson-deserialize-json-unknown-properties

Spring Bean Scopes: Bean 实例生成方式与生命周期

The life cycle and visibility of Spring Bean.

6 types of scopes

singleton

prototype

request

session

application

websocket

The last four scopes mentioned, request, session, application and websocket, are only available in a web-aware application.

使用例子

@Bean@Scope("singleton")publicPersonpersonSingleton(){

return new Person();

}

@Service

@Scope("prototype")

public class SpecificationParserClickHouse implements SpecificationParser {

@Resource

FieldFeatureBuilder fieldFeatureBuilder;

ListspecList;

IQuerySpecification spec =null;

...

}

参考资料

https://www.baeldung.com/spring-bean-scopes

"Connection refused" when syncing Maven project in 2021.1 : java.rmi.ConnectException: Connection...

解决方案

settings—》Build,Execution,Deployment—》Build Tools—》Maven—》Importing—》JDK for importer选择你安装的JDK版本

如果还是解决不了问题,可以尝试添加如下两个参数:

maven -> Importing -> VM options for importer

-Xmx768m -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Djava.net.preferIPv4Stack=true

maven -> Runner -> VM Options

-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true -Djava.net.preferIPv4Stack=true

注意:

如果已经添加了上面两个参数,还是会出现那个问题,需要移除【移除后需要Application一下】,然后重新添加

https://blog.csdn.net/qq_17555933/article/details/113735428

相关问题

Since not so long ago I often get Sync error "Cannot reconnect" when pressing a sync button after editing a pom.xml file.

I then press the sync button again and the problem disappears.

Caused by: java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:

java.net.ConnectException: Connection refused: connect

at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:623) at java.rmi/sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:209) at java.rmi/sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:196) at java.rmi/sun.rmi.server.UnicastRef.invoke(UnicastRef.java:132) at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:217) at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:162) at com.sun.proxy.$Proxy164.applyProfiles(Unknown Source) at jdk.internal.reflect.GeneratedMethodAccessor382.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at com.intellij.execution.rmi.RemoteUtil.invokeRemote(RemoteUtil.java:150)

This also seems to happen when downloading sources of a dependency (Maven project). I noticed a reboot of IntelliJ helps. But only a while, after that the same:

java.lang.RuntimeException: Cannot reconnect.

at org.jetbrains.idea.maven.server.RemoteObjectWrapper.perform(RemoteObjectWrapper.java:82)

at org.jetbrains.idea.maven.server.MavenEmbedderWrapper.customizeForResolve(MavenEmbedderWrapper.java:52)

at org.jetbrains.idea.maven.project.MavenProjectResolver.downloadSourcesAndJavadocs(MavenProjectResolver.java:253)

at org.jetbrains.idea.maven.project.MavenProjectsProcessorArtifactsDownloadingTask.perform(MavenProjectsProcessorArtifactsDownloadingTask.java:40)

at org.jetbrains.idea.maven.project.MavenProjectsProcessor.doProcessPendingTasks(MavenProjectsProcessor.java:145)

at org.jetbrains.idea.maven.project.MavenProjectsProcessor.access$000(MavenProjectsProcessor.java:36)

at org.jetbrains.idea.maven.project.MavenProjectsProcessor$1.run(MavenProjectsProcessor.java:114)

at org.jetbrains.idea.maven.utils.MavenUtil.lambda$runInBackground$5(MavenUtil.java:494)

at com.intellij.util.RunnableCallable.call(RunnableCallable.java:20)

at com.intellij.util.RunnableCallable.call(RunnableCallable.java:11)

at com.intellij.openapi.application.impl.ApplicationImpl$1.call(ApplicationImpl.java:268)

at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)

at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:668)

at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:665)

at java.base/java.security.AccessController.doPrivileged(Native Method)

at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1.run(Executors.java:665)

at java.base/java.lang.Thread.run(Thread.java:834)

Caused by: java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is:

java.net.ConnectException: Connection refused (Connection refused)

at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:623)

at java.rmi/sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:209)

at java.rmi/sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:196)

at java.rmi/sun.rmi.server.UnicastRef.invoke(UnicastRef.java:132)

at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:217)

at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:162)

at com.sun.proxy.$Proxy126.createEmbedder(Unknown Source)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:566)

at com.intellij.execution.rmi.RemoteUtil.invokeRemote(RemoteUtil.java:155)

at com.intellij.execution.rmi.RemoteUtil.access$400(RemoteUtil.java:25)

(and yes, firewall is set up correctly, as i said it works for a while as well).

https://youtrack.jetbrains.com/issue/IDEA-263673

https://intellij-support.jetbrains.com/hc/en-us/articles/360014262940-Unable-to-import-Maven-project-import-fails-with-error-Cannot-reconnect

Spring 事件框架 ApplicationEvent & 观察者模式(Publisher -> Listener)

Spring 事件框架 ApplicationEvent & 观察者模式(Publisher -> Listener)

Event 事件机制概述

事件机制在一些大型项目中被经常使用,于是 Spring 专门提供了一套事件机制的接口,方便我们运用。本文来说说 ApplicationEventPublisher 的使用。

在设计模式中,观察者模式可以算得上是一个非常经典的行为型设计模式.

java 和 spring 中都拥有 Event 的抽象,分别代表了语言级别和三方框架级别对事件的支持。

Spring 的文档对 Event 的支持翻译之后描述如下:

ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。

ApplicationEvent 是由 Spring 提供的所有 Event 类的基类,为了简单起见,注册事件只传递了 name(可以复杂的对象,但注意要了解清楚序列化机制)。

需要注意的是,服务必须交给 Spring 容器托管。ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。用户注册后,不再是显示调用其他的业务 Service,而是发布一个用户注册事件。

事件订阅者的服务同样需要托管于 Spring 容器,ApplicationListener 接口是由 Spring 提供的事件订阅者必须实现的接口,我们一般把该 Service 关心的事件类型作为泛型传入。处理事件,通过 event.getSource() 即可拿到事件的具体内容.

核心类说明

1.ApplicationEventPublisherAware

ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口. 使用这个接口,我们自己的 Service 就拥有了发布事件的能力。用户注册后,不再是显示调用其他的业务 Service,而是发布一个用户注册事件。

2.ApplicationListener

ApplicationListener接口是由 Spring 提供的事件订阅者必须实现的接口,我们一般把该 Service 关心的事件类型作为泛型传入。

处理事件: 通过 event.getSource() 即可拿到事件的具体内容

3.ApplicationEventPublisher

ApplicationEventPublisher是ApplicationContext的父接口之一。这接口的作用是:Interface that encapsulates event publication functionality.

功能: 发布事件,也就是把某个事件告诉的所有与这个事件相关的监听器。

下面看一个 ApplicationEventPublisher 的使用实例。

/**

 *  事件基类

 */

public class KunLunEvent extends ApplicationEvent {

private TaskInstanceDTO taskInstanceDTO;

private EventType eventType;

public KunLunEvent(Object source, TaskInstanceDTO taskInstanceDTO) {

super(source);

this.taskInstanceDTO=taskInstanceDTO;

}

public TaskInstanceDTO getTaskInstance(){

return taskInstanceDTO;

}

public EventType getEventType() {

return eventType;

}

public void setEventType(EventType eventType) {

this.eventType = eventType;

}

}

/**

 *事件处理器

 */

public interface KunLunHandler {

/**

     *处理具体业务逻辑

     * @param kunLunEvent

     */

    void handle(KunLunEvent kunLunEvent);

Boolean match(KunLunEvent kunLunEvent);

}

import org.springframework.context.event.EventListener;

import org.springframework.scheduling.annotation.Async;

import org.springframework.stereotype.Component;

import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;

import java.util.List;

/**

 *事件监听器

 */

@Component

public class KunLunListener {

@Resource

List handlerList;

@Async

@EventListener

public void onApplicationEvent(KunLunEvent kunLunEvent) {

if(!CollectionUtils.isEmpty(handlerList)){

handlerList.stream()

.filter(handler -> handler.match(kunLunEvent))

.forEach(handler -> handler.handle(kunLunEvent));

}

}

}

具体的Handler 实现:

@Service

public class CrowdHiveResultHandler implements KunLunHandler {}

@Service

public class CrowdAbaseResultHandler implements KunLunHandler {}

事件发布器

@Service

public class DoradoWebHookServiceImpl implements ApplicationContextAware {

private ApplicationContext applicationContext;

@Override

public void handle() {

// ….

// 推送异步消息

        publishEvent(doradoWebHookResponse);

}

/**

     *发送异步消息

     */

    private void publishEvent() {

// …

applicationContext.publishEvent(event);

}

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

this.applicationContext = applicationContext;

}

}

参考资料

https://www.baeldung.com/spring-events

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEventPublisher.html

fastjson 解析报错 com.alibaba.fastjson.JSONException: create instance error...

用fastJson解析报 create instance error的错误

认真检查,bean类内的字段都和服务端返回的字段一致,格式都是正确的,为什么会报错呢。

在网上找到答案,如果存在内嵌的情况:

报错代码:

public class  A{

private String haha;

private  int  gogo;

private B  bb;

public class B {

private String name;

private  int  price;

}

}

B嵌套在A里,那么我们要声明内嵌类static属性, 如下(这样问题解决)

正确代码:

public class  A{

private String haha;

private  int  gogo;

private B  bb;

public static class B{

private String name;

private  int  price;

}

}

原理解析

这个问题主要是因为Java内部类和嵌套类的实现机制造成的.

静态的内部类叫做嵌套类,那么两者有什么区别呢?

一般的内部类虽然在源代码中没有写,但是编译后会看到里面多了一个指向外部类的引用,如果使用内部类进行json序列化,会因为找不到它的外部类而报异常,而嵌套类因为是静态的,里面没有默认的外部类的引用,即使没有外部类的对象也能够使用,因此在json转换的时候不会报错。

怎样进行 maven 离线编译 install

怎样进行 maven 离线编译 install

mvn clean install  --offline

或者在settings中配置:

<offline>true< /offline>

开启离线模式.

Spring Boot 项目访问依赖 jar 包内部的资源文件的路径问题详解

Spring Boot 项目访问依赖 jar 包内部的资源文件的路径问题详解

核心代码行:

Resource[]resources =new PathMatchingResourcePatternResolver().getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +"META-INF/driver.properties");

直接上源码:

package com.bytedance.kunlun.engine.drivermanager;

import java.io.InputStream;

import java.net.URL;

import java.util.*;

import com.bytedance.kunlun.sdk.drivermanager.api.Driver;

import org.springframework.core.io.Resource;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.springframework.core.io.support.ResourcePatternResolver;

public class KunLunDriverManager {

/**

* 注册进系统的驱动列表

*/

private final static MapregisteredDrivers =new HashMap<>();

/**

* 驱动配置,配置文件名

*/

private static final String DRIVER_PROPS_FILE ="META-INF/driver.properties";

/**

* 驱动配置Key,Driver实现class全名

*/

private static final String KEY_DRIVER_IMPL ="driver_class_name";

/**

* 驱动配置Key,Driver名称

*/

private static final String KEY_DRIVER_NAME ="driver_name";

/**

* 防止类显示初始化调用

*/

private KunLunDriverManager() {

}

static {

try {

loadInitialDrivers();

}catch (Exception e) {

throw new RuntimeException(e);

}

}

public static Driver getDriver(String driverName) {

return registeredDrivers.get(driverName);

}

/**

* 注入driver

*/

private static void registerDriver(String driverName,Driver driver)throws Exception {

if (registeredDrivers.containsKey(driverName)) {

throw new Exception("引擎名称重复");

}

registeredDrivers.put(driverName, driver);

}

/**

* 扫classpath下的所有DRIVER_PROPS_FILE文件,反射实例化,注入DriverManager

*/

private static void loadInitialDrivers()throws Exception {

Resource[]resources =new PathMatchingResourcePatternResolver().getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +"META-INF/driver.properties");

for (int i =0; i

Resource res =resources[i];

URL url =res.getURL();

InputStream in =url.openStream();

Properties props =new Properties();

props.load(in);

// 通过反射 new driver instance

Driver driver =newDriverByParseProperties(props);

if (driver !=null) {

registerDriver(props.getProperty(KEY_DRIVER_NAME),driver);

}

}

}

/**

* 根据Properties配置反射生成Driver

*/

private static Driver newDriverByParseProperties(Properties props)throws Exception {

Classclazz =Class.forName(props.getProperty(KEY_DRIVER_IMPL));

Driver driver = (Driver)clazz.getDeclaredConstructor().newInstance();

return driver.setUp(props);

}

}

Spring Boot 项目打包问题集锦: jar依赖多出boot-inf 文件夹问题/多环境动态打包/缺少BOOT-INF目录问题等

Spring Boot 项目打包 boot-inf 文件夹的问题

spring-boot maven打包,一般pom.xml文件里会加

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

这样打的jar里会多一个目录BOOT-INF。

引起问题: 程序包不存在。

解决办法: 如果A子模块包依赖了B子模块包,在B子模块的pom文件,加入 configuration.skip = true

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<skip>true</skip>

</configuration>

</plugin>

Spring Boot Maven插件打包后,包内没有BOOT-INF目录

使用maven插件打包后,发现包很小100来kb,显然是不对,包内缺少BOOT-INF目录,BOOT-INF是用于存放引用的外部lib的,所以缺少,打出来的包根本不能运行:

解决办法:

在自己项目的pom中,或者父pom中,在plugin中添加executions节点代码,重新打包即可解决。

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<executions>

<execution>

<goals>

<goal>repackage</goal>

</goals>

</execution>

</executions>

</plugin>

SpringBoot中如何引入本地jar包,并通过maven把项目成功打包成jar包部署

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<includeSystemScope>true</includeSystemScope>

</configuration>

</plugin>

</plugins>

</build>

在sprinboot项目中pom.xml文件加<includeSystemScope>true</includeSystemScope>,代表maven打包时会将外部引入的jar包(比如在根目录下或resource文件下新加外部jar包)打包到项目jar,在服务器上项目才能运行,不加此配置,本地可以运行,因为本地可以再lib下找到外部包,但是服务器上jar中是没有的。

Spring Boot项目动态打包

build节点新增,如下代码:

<resources>

<resource>

<directory>src/main/resources/</directory>

<excludes>

<exclude>application.yml</exclude>

</excludes>

<filtering>false</filtering>

</resource>

<resource>

<directory>src/main/resources/</directory>

<includes>

<include>application.yml</include>

</includes>

<!-- 用参数化值替换文件文本内容(如@profileActive@)  -->

<filtering>true</filtering>

</resource>

</resources>

然后pom.xml文件继续添加以下配置

<profiles>

<profile>

<!-- 本地开发环境打包:mvn clean package -P dev -->

<id>dev</id>

<properties>

<profileActive>druid,dev</profileActive>

</properties>

<activation>

<activeByDefault>true</activeByDefault>

</activation>

</profile>

<profile>

<!-- 测试环境打包:  mvn clean package -P test -->

<id>test</id>

<properties>

<profileActive>druid,test</profileActive>

</properties>

</profile>

<profile>

<!-- 生产环境打包:  mvn clean package -P prod -->

<id>prod</id>

<properties>

<profileActive>druid,prod</profileActive>

</properties>

</profile>

<profile>

<!-- 预发布环境打包:  mvn clean package -P pre -->

<id>pre</id>

<properties>

<profileActive>druid,prod,pre</profileActive>

</properties>

</profile>

</profiles>

application.yml文件中配置

spring:

profiles:

active:  @profileActive@

@profileActive@打包时,会被动态替换成profileActive节点的值。

编程实践精华总结集锦系列1: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等相关推荐

  1. 编程实践精华总结集锦系列2: SpringBoot/Maven/IDEA/Java/Kotlin/Redis等等

    Go Quick Start 极简教程 Go 官网:https://golang.org/ 文档:https://golang.org/doc/ 开发环境配置 下载 Go: (官网)https://g ...

  2. Docker学习总结(20)——Docker 容器实践精华问答集锦

    问题一:请问如何做好容器的安全性管理?跨主机容器间通信如何来管理(比如:ip 分配,划vlan 等)?k8s对于容器的监控和集群管理,该如何来做? 精灵云:关于安全,容器自带一些安全技术比如Capab ...

  3. Java项目:在线点餐系统(java+Springboot+Maven+mybatis+Vue+mysql+Redis)

    源码获取:博客首页 "资源" 里下载! 项目描述: 这是一个基于SpringBoot+Vue框架开发的在线点餐系统.首先,这是一个前后端分离的项目.具有一个在线点餐系统该有的所有功 ...

  4. 基于华为云obs实现文件上传下载(技术栈mysql+springboot+Maven+jsp+java)的技术分享

    基于华为云obs实现文件上传下载(技术栈mysql+springboot+jsp+java)的技术分享 obs实现文件上传下载 前言 一.OBS是什么? 二.使用步骤 1.1 前期准备 2 工具的内容 ...

  5. 【编程实践】Linux / UNIX Shell编程极简教程

    不同于一般的介绍Linux Shell 的文章,本文并未花大篇幅去介绍 Shell 语法,而是以面向"对象" 的方式引入大量的实例介绍 Shell 日常操作,"对象&qu ...

  6. 【编程实践】Google Guava 极简教程

    前言 Guava 工程包含了若干被 Google 的 Java 项目广泛依赖 的核心库,我们希望通过此文档为 Guava 中最流行和最强大的功能,提供更具可读性和解释性的说明. 适用人群 本教程是基础 ...

  7. Java并发编程实战_一线大厂架构师整理:java并发编程实践教程

    并发编程是Java语言的重要特性之一, 在Java平台上提供了许多基本的并发功能来辅助开发多线程应用程序.然而,这些相对底层的并发功能与上层应用程序的并发语义之间并不存在一种简单而直观的映射关系.因此 ...

  8. 程序员编程艺术第一~十章集锦与总结(教你如何编程)--持续更新中

    程序员编程艺术第一~十章集锦与总结--面试.算法.编程 作者:July.编程艺术室. 出处:http://blog.csdn.net/v_JULY_v . 围绕"面试"." ...

  9. 嵌入式C/C++语言精华文章集锦

    嵌入式C/C++语言精华文章集锦 C/C+语言struct 深层探索.................................................................. ...

  10. 【组队学习】【29期】1. 编程实践(数据可视化)

    1. 编程实践(数据可视化) 航路开辟者:杨剑砺.杨煜.耿远昊.李运佳.居凤霞 领航员:范佳慧 航海士:杨剑砺.伊雪.肖桐.李晴晴.蔡婧旎 基本信息 开源内容:https://github.com/d ...

最新文章

  1. 使用python 下载_使用python下载大量文件
  2. v-contextmenu的使用(右键菜单)
  3. ubuntu17.04之apt-get源
  4. 一个java类运行时从哪个方法开始_Java的应用程序是从类中的
  5. 数学建模之图论——图与网络模型(一)(基本概念和最短路问题,附MATLAB源码)
  6. java arraylist 合并_在Java中将两个arrayList合并到一个新的arrayList中,没有重复且没有顺序...
  7. Python Tricks(一)—— np.where 与最大最小截断
  8. Mybatis ResultMap Collection 复合主键
  9. linux下c编程常识
  10. css背景颜色渐变 从左到右 从下到上
  11. python爬豆瓣top250书籍_python爬虫练习-爬取豆瓣图书top250
  12. 遇见逆水寒服务器维护时间,《遇见逆水寒》7月23日更新公告
  13. C. Divan and bitwise operations
  14. [Anaconda] 不需要重装环境,解决引用scipy包时Could not find module ‘...gfortran-win_amd64.dll‘问题
  15. 创邻科技Galaxybase荣获2021年度IT168技术卓越奖
  16. 创建AccessToken工具类
  17. 密码学基础-AES加解密Python代码实现
  18. 2021-11-20U盘写入Ubuntu镜像中断后,U盘无法格式化
  19. 神秘国度的爱情故事 数据结构课设-广州大学
  20. 【点的定位】Save the Students! UVALive - 5984

热门文章

  1. JAVA-MYSQL-SSH酒店民宿客房管理系统
  2. OTL/OCL/BTL/甲类/乙类/甲乙类
  3. 配置telnet远程登录管理二层交换机
  4. 苹果旧版app_苹果手机教你安装旧版app
  5. Outlook连接Gmail不成功可以试试以下方法
  6. 怎么恢复oracle的包,Oracle的恢复管理器及DBMS_JOB包分析
  7. 计算机网络 之 P2P架构
  8. 软件工程与计算II-24-考试总结
  9. [系统安全] 逆向工程进阶篇之对抗逆向分析
  10. java public aspect_ASPECT Java的使用