好代码的五个特质

news2025/1/11 11:09:59

CUPID出自Daniel的一篇名为《CUPID—for joyful coding》的博文,即《CUPID-为了快乐编程》。CUPID是Composable/Unix philosophy/Predictable/Idiomatic/Domain based几个单词的缩写,有经验的同学一看就知道这是好代码的一些属性。知道Cupid这个单词的同学还能感受到这一组属性所蕴含的对于软件工程的热情。Cupid的中文是丘比特,是指古罗马的爱神,其意象是一个长有翅膀的小孩,拿着弓箭射向人们,以便人们可以相互爱上对方。
好代码的五个特质

特质

Daniel老爷子回忆了自己三十多年的编程经历,他发现在修改代码时,好的代码会给人一种非常愉悦的感觉。你可以轻松找到需要修改的地方,而且,那个地方的代码是如此的易于理解,以至于一眼就能看出来代码在干什么。你可以很自信的完成修改,并且确信不会引入额外的副作用。代码是那么的鲜活,它会主动的指引你去你想去的地方,并且热情的欢迎你四处游览,就像在你熟悉的家里一样!

为什么好的代码能有这样的魅力?什么样的代码才是好代码?提到这个问题,我们常常会想到SOLID(Single Responsibility/Open-close/Liskov Substitution/Interface Segregation/Dependency Injection)原则,Daniel老爷子认为应该存在比SOLID更好用的东西。

如何衡量代码好坏?SOLID采用了一组原则来定义好的代码,但是原则更像是规则,要么符合,要么不符合。而软件开发过程非常复杂,其间充满了平衡和妥协,事实上并没有一种非黑即白的规则可以适用。有没有比原则更好的选择?它可能是特质(Properties/Characteristics)。

特质是事物本身所具备的,而不是靠一组规则去定义的;特质吸引我们去深度挖掘,而不是信任已有的总结;特质通常不是简单的0或1的判断,而是一种从低到高的程度;特质是从观察者的角度给出的,更关注观察者的体验,而更少关注与体验无关的其他方面。

之所以我们会觉得某样东西是好的,常常是因为某样东西具备了一些好的特质。比如蓝天白云图,它具备了干净、纯粹的特质。比如勾股定理和质能方程,它们具备简洁、优雅的特质。

如果说好的代码是一个中心点,特质就像是定义了一些方向,通过这些方向的指引,就可以不断向这个中心点靠拢。

CUPID就是从特质的角度来定义的,它尝试用一组助记词来指示好代码所具备的一组特质,并希望这组特质是最重要的特质。

CUPID所指出的方向与SOLID定义的原则并不冲突,只是角度不同,CUPID更多站在代码的用户–将来修改代码的人–的视角来看待代码,并指出了好的代码应该具备的特质。从这个角度来讲,CUPID比SOLID的适用性更广(SOLID事实上只是针对面向对象设计提出的)。比如,给出一段代码,用SOLID可能并不能判断好坏,因为这段代码可能根本不涉及SOLID中提到的几个原则(比如函数式风格的代码)。但是很大可能可以从CUPID指明的几个方向来得到一些结论。

CUPID是完备的吗,很遗憾,也不是。但CUPID所指出的五种特质可能是按照重要程度排序之后取前五的特质。

理解CUPID

下面我们一起看看CUPID到底是什么,以及,如何用CUPID来帮助我们写出好的代码。

下面的内容,部分来自Daniel老爷子的原文,部分结合了个人的心得体会,分享给大家。

可组合特质(C)

CUPID的第一个字母C是指Composable,即可组合特质。

近两年,我们在讨论面向对象程序设计的时候,越来越关注到“组合优于继承”这样的原则。作为面向对象程序设计的三大特征之一的“继承”,似乎正越来越受到挑战,这一部分原因是很多继承的设计是不合理的,比如不符合SOLID所指出的里氏代换原则。另一部分原因在于,过深的继承树带来了代码的可理解性问题,因为我们总是需要理解了基类才能理解子类。其实继承也是很有用的,但其前提是设计合理的继承。“组合优于继承”就是告诉我们优先考虑用组合模式来进行设计。

可组合还体现在以下三个方面:

精巧的接口

接口太多时,读者需要知道如何组合这些接口去完成某个功能,而接口较少时,读者可以更容易学习并更少犯错。只对外公开一个模块来提供接口,比对外公开多个模块提供接口更好。只对外公开一个类来提供接口,比对外公开多个类提供接口更好。

正确的接口粒度设计比较困难,最佳的粒度是接口既不显得臃肿也不碎片化。

设计模式中有一种常见的模式Facade,即门面模式,其意图正是将对外公开的接口放到一个类中去提供,以便减少接口面,从而让接口更容易使用。

可体现意图的代码

可体现意图的代码是用业务语言编写且能反映业务过程的代码。可体现意图的代码可以使读者更容易弄清为什么代码要这么写,因此更容易组合使用。代码中的各类命名(比如变量、函数等)都可以用于将意图体现得更为明显。

比如以下意图不明的代码:

def getTodos(todos, users):
    todos = [todo for todo in todo if not todo.completed and todo.type == ‘HOME’]
    todos = [{‘title’: todo.title, ‘user_name’: users[todo.userId].name} for todo in todo]
    todos.sort(key=lambda todo: todo[’user_name’])
return todos

可以重构为以下意图明确的代码:

def get_todos(todos, users):
    top_priority_todos = [todo for todo in todo if not todo.completed and todo.type == ‘HOME’]
    todo_view_models = [{‘title’: todo.title, ‘user_name’: users[todo.userId].name} for todo in top_priority_todos]
    todo_view_models.sort(key=lambda todo: todo[’user_name’])
return todo_view_models

或者:

def get_todos(todos, users):
    is_top_priority = lambda todo: not todo.completed and todo.type == ‘HOME’
    todos = [todo for todo in todo if is_top_priority(todo)]
    to_view_model = lambda todo: {‘title’: todo.title, ‘user_name’: users[todo.userId].name}
    todos = [to_view_model(todo) for todo in todo]
    user_name = lambda todo: todo[’user_name’]
    todos.sort(key=user_name)
return todos

最小依赖

拥有最小依赖的代码是容易组合使用的。

当一个库有大量的依赖时,一旦使用了这个库就会间接引入这些依赖。这不仅使我们发布的二进制制品变得臃肿,也很容易引起一些依赖库的版本冲突问题。大家如果做过Hadoop的MapReduce任务开发,应该对这个问题深有体会,因为Hadoop本身有大量的Java依赖,如果我们在MapReduce任务中不小心引入了一个和Hadoop本身的依赖不兼容的版本,在任务运行时就会出错。

一个拥有最小依赖的库是很容易使用的,上述包冲突问题会更少发生。

我常常在项目中见到有人为了实现一些很简单的功能而引入没必要的依赖。比如,当我们面对的问题只是简单的查询ElasticSearch服务中的数据时,就要评估一下是否有必要引入ElasticSearch的客户端库依赖,因为我们可以很容易的使用通用的HTTP工具库来发送一个请求来实现数据查询。

面向对象程序设计有一个重要的原则,即迪米特法则(Law of Demeter),又被称为最小知识原则、不要和陌生人说话原则。其指导意义在于一个类不应该和与其不相关的类产生(依赖)关系。

Unix哲学(U)

CUPID的第二个特质U即是指Unix哲学。

Unix可以说是当今应用最广泛的操作系统,不管是云服务器还是个人电脑抑或智能手机、IoT设备,都有Unix的影子。Unix广泛的以Linux、MacOS、iOS、Android等等操作系统的形式存在着。为什么Unix可以如此成功?这得益于Unix的简单而一致的设计哲学。

CUPID中的Unix哲学主要指其最重要的一个观点:一个程序应该做一件事,并将其做好。Unix中的大量程序都很好的提现了这一特质,比如ls程序只做列举文件的事,而要查看文件详情,则需要使用lstat,查看文件内容使用cat,搜索文件内容使用grep等等。如果我们查看这些程序的使用手册(Manual Page),将发现每一个程序都提供了很多的参数供选择,事实上每个程序的功能都很强大,并处理了大量的异常情况。这就是把一件事做好的体现。

Unix操作系统中定义了一个强大的管道(Pipe)概念,一个程序的输出可以通过管道传输给另一个程序,从而简单而一致的实现了多个程序的组合使用。比如ls命令可以列举出文件列表,然后将结果传输给wc程序统计数量,就可以简单的计算出目录中的文件数量。

只做好一件事与SOLID中的单一职责原则很像。但是Unix哲学的出发点是读者,从读者角度来看程序,得出程序应该只做好一件事的结论。单一职责原则则是从代码的角度出发进行描述的。Unix哲学更多描述的是程序的目的,并指明一个程序应该只有一个目的。

与Unix原则描述很相似的还有关注点分离的原则。关注点分离是指不同的模块应该关注不同的事情。比如分层设计,每一层的关注点应该不一样:MVC中的M关注业务模型和业务逻辑,V关注展示,C关注交互逻辑;TCP/IP四层网络模型中物理层关注物理链路,网络层关心节点链路如何建立,传输层关注数据发送的可靠性,应用层关注在具体的应用协议。

可预测性(P)

CUPID的第三个特质P是指Predictable,可预测性。

程序的可预测性是指它应该做它看起来要做的事情,一致且可靠,不隐藏任何出乎意料的行为。

可预测性包括三个方面:1. 与期望一致的行为;2. 输出确定的结果;3. 内部行为可观测。

与期望一致的行为

我们可以通过测试来定义所期望的程序的行为,但是并不是一定需要用测试来让程序与期望的行为一致。精心的挑选名字,克制的编写逻辑,正确的处理异常这些都能使得程序与期望的行为一致。

读操作和写操作常常被分开对待。读操作不会对程序状态产生影响,我们可以安全的调用,不用顾忌太多后果。写操作用于修改程序状态,因此,在使用时需要特别小心,比如如果有多线程访问就需要考虑线程安全,同时操作多个状态就需要考虑事务一致性。

如何在读操作和写操作中保持与期望一致的行为?那就是读操作中不应该隐藏某些让人意外的写操作。

输出确定性的结果

具备确定性的程序很容易让人理解和使用,因为它在任何一次调用都会返回同样的结果,我们可以明确的知道它将返回什么。

我们常说易于推理的代码是好代码,具备确定性的就具备易于推理的特性。

大概是由于Web前端技术的飞速发展,近些年函数式编程范式得到广大开发者的亲睐。函数式编程范式中最重要的一个概念就是纯函数。纯函数是指没有任何副作用且可以输出确定的结果的函数。

纯函数是更容易测试的,我们对使用它的信心也更强。但是,在函数式编程范式中,对纯函数的规范定义显得学院化,并加入了场景限定。事实上,我们主要需要的是程序的确定性。用面向对象范式编程,可以考虑把一个对象设计成值对象,这样也可以增强程序的确定性。由于不确定性常常来自复杂且不确定的依赖(比如,某个依赖自己管理了复杂的状态,就也会间接的使你的代码充满不确定性),在设计类时,严格控制其依赖的外部模块,尽量做到无依赖,也可以增强程序的确定性。

具备确定性的代码通常是健壮、可靠而具备弹性的。

内部行为可观测

如何预测程序的行为?观察它的运行时输出是一个很好的方法。如果程序可以在运行时打印关键的内部状态或行为就可以让我们推测其当前状态。

观察程序内部状态可以分为以下几个级别:

  • 信息仪表(Instrumentation): 程序告诉我们它正在干什么
  • 遥测(Telemetry): 将程序告诉我们的信息以一种接口暴露出来,使其可以被远程访问
  • 监控(Monitoring): 将程序告诉我们的信息可视化出来
  • 告警(Alerting): 从监控信息中识别异常,发出通知
  • 预测(Predicting): 利用监控信息来预测即将发生的事件
  • 自适应(Adapting): 通过告警的或者预测的信息动态调整系统以适应变化

有一些工具可以自动提取程序运行时信息供分析,但是最佳的提升程序的可观测性的方式还是通过有意识的设计来在关键处输出程序的状态或行为。

符合惯例的(I)

CUPID的第四个特质I是指Idiomatic,符合惯例的。

大家都有自己的编码习惯,这些习惯包括空格和制表符的使用,变量命名规则,括号放置位置,代码结构,提交的粒度和注释等等。这些不一样的习惯将显著的增加不熟悉代码库的读者的认知负载。读者不仅需要理解问题空间和解空间,还需要不断进行翻译,以便识别当前的代码是有意编写的,还是无意的,或者只是作者的习惯而已。

编写代码时的最伟大的特质是同情心:对你的代码的用户的同情;对提供支持服务的同事的同情;对将来修改代码的开发者的同情。事实上,他们中任意一个可能就是将来的你。编写“人类可读的代码”意味着为别人编写代码。这正是“符合惯例”的意义。

编写代码时,可以假定你的用户具备以下背景:

  • 熟悉所使用的编程语言,及该语言对应的库、工具链和生态
  • 懂软件开发的有经验的开发者

还有一条,他们正努力的完成某件事情。

语言惯例

代码应该遵循编程语言的惯例。有些编程语言在代码风格上态度鲜明,我们会更容易判断代码是否符合语言惯例。另一些编程语言则可以兼容多种不同风格,此时我们应该选择一种风格,并始终坚持它。

Python是一门在代码风格上态度鲜明的语言。当我们在Python的交互式命令行中输入import this,或者运行命令python -m this时,就会发现输出了Python所推荐的编程风格。这些编程风格组合成了”Python之禅”(The Zen of Python)。比如“应该有一种显然的实现方式,而且最好只有一种”(There should be one-- and preferably only one --obvious way to do it)。

Go语言内置了一个代码格式化工具gofmt,它会处理缩进、括号位置等问题,可以使所有代码变得风格一致。除此之外,还有一篇专门说明Go语言风格的文档Effective Go来指导大家写出风格一致的代码。

语言惯例出现在各个级别的代码中,函数名、类型、参数、模块、代码组织结构、模块组织结构、工具链选择、依赖选择、管理依赖的方式等。如果你的代码符合这些语言惯例,将会读起来更让人愉悦。

如何让代码遵循这些语言惯例?可能没有什么更好的办法,只有让自己多去学习这些惯例。

团队惯例

当编程语言本身没有风格倾向,或者有多种风格可选的时候,用什么风格来写代码就由我们自己或者我们的团队来决定了。通常团队会自己定义一些惯例,比如用什么工具,如何缩进等。借助各种语言的代码检查工具,可以自动化的让代码保持一致的风格。

对于某些无法用工具覆盖的惯例,利用**架构设计决策记录**来文档化这些惯例是一种好的实践。这些惯例的重要性并不比其他的架构设计决策更低。

基于领域的(D)

CUPID的最后一个特质D是指Domain based,基于领域的。

近几年,微服务的兴起使得领域驱动设计(Domain Driven Design, 简称DDD)以新的面貌受到大家的广泛关注。相对于其对于微服务设计的指导意义,DDD提出的以领域为中心的软件开发思想或许具有更重大的意义。

基于领域的语言

由于代码的读者通常对问题是清楚的,所以,代码应该用问题空间的语言来写,这样就能让代码的读者更容易的理解。问题空间语言即领域语言。

编程语言和库里面充满了计算机技术术语。比如常用的数据结构,如数组、链表、哈希表、树结构等。还比如数据库表、网络连接等。甚至基础数据类型,如整型数值、浮点型数值、字符串等也都是技术术语。直接在代码中使用这些术语不会告诉你的读者你要解决什么问题,他们需要根据对问题的理解进行翻译。

TDD可以用于帮助我们更多的用领域语言编写代码。TDD要求在还没有实现代码的时候写出测试代码。如何做到呢?其实,TDD是希望我们可以在看到问题后,先用自然语言描述测试过程,然后再将自然语言的测试过程翻译为编程语言。由于描述测试过程时,会站在用户的角度进行描述,所以将更多的使用领域语言。并且测试过程的描述将反映出程序应该有的公开接口,所以接口也会变成用领域语言描述的接口,这就很大程度上促进了用领域语言编写代码。

举个例子,在电商场景中,如果要实现购物车的功能,则分析购物车的业务需求之后,可以将测试过程描述如下:

- 准备一个空的购物车 - 向购物车添加商品1,数量1 - 向购物车添加商品2,数量2 - 购物车中应该有两种商品,其中有1个商品1及2个商品2 - 向购物车添加商品1,数量1 - 购物车中应该有两种商品,其中有2个商品1及2个商品2 - 从购物车取出商品1,数量2 - 购物车中应该有一种商品,即2个商品2

翻译为Java语言的测试代码示例如下(部分):

...
void testCart() {
    var cart = new Cart();
    var product1 = new Product();
    var product2 = new Product();

    cart.add(product1, 1);
    cart.add(product2, 2);

    assertTrue(cart.contains(product1));
    assertEquals(1, cart.productCount(product1));
    assertTrue(cart.contains(product2));
    assertEquals(2, cart.productCount(product2));

    cart.add(product1, 1);
    ......
}
...

可以看到,通过编写测试,我们用领域语言设计了Cart类,Product类,并且对Cart类设计了add contains productCount三个方法。除了促进使用领域语言编写代码,TDD还可以让我们提供的接口刚刚够用,不多不少,从而实现可组合性特质中的“精巧的接口”。

使用领域语言编写代码的最佳状态是,我们的代码可以让没有技术背景的业务人员也能轻松看懂,整个代码读起来就像业务分析师在讲解业务逻辑一样。

基于领域的结构

除了使用领域语言编写代码,在模块的设计、代码目录结构(或包结构)也应该优先使用领域语言命名。

很多使用Spring框架的Java程序员有个偏好,他们按照框架提供的概念来组织代码,并且将不同的文件按照框架概念进行分类存放。一个可能的结构可能是:

app
|----controllers
|----assets
|----models
|----events
|----repositories
|----requests
|----responses
|----dtos
|----configurations
......

这带来的问题是,当要修改一个API时,不得不在多个目录中去查找和修改代码。这不仅增加了认知负载,使代码耦合在一起,还增加了修改代码的负担。

使用基于领域的结构,建议尽量将目录按照领域进行划分,而不是框架概念。比如,如果是一个电商的场景,目录结构应该是user product order payment shipment等。

当前一个流行的架构模式是分层架构,如果按照分层架构进行设计,则顶层目录可以是不同的分层名称,分层以下,就应该是由领域概念组成的目录。并且分层之间应该有严格的依赖顺序,不应产生两个分层循环依赖的情况。虽然看起来这是一个例外,但是这种拆分是有缺陷的。近两年微服务架构非常流行,而微服务的拆分是按照业务领域进行拆分的,这可以理解为微服务是整体产品这个根目录下的基于领域的子目录。这个现象可以理解为大家对于分层架构的目录划分并不满意,还是希望在更上层基于领域来划分目录。

基于领域的边界

无论我们如何组织代码结构,目录(或模块)的边界变成了事实上的领域边界。一打开代码库就能看到目录结构,目录的层级和名字逐渐变成了大家最熟系的信息。所以,在设计上,一个重要的原则就是将领域划分和目录划分保持一致。这将有效降低团队的认知负载,开发者将因此而更不容易犯错,团队效率最终将得到提高。

这并不意味着需要组织成一个平坦(flat)的目录结构。领域以下可以有子领域,目录以下可以有子目录,模块以下可以有子模块。重要的是这一个一个层级需要能对应上。

总结

到这里,我们应该了解了CUPID所指出的五种特质的内涵。可以明显的看到,相比不符合CUPID特性的代码,符合CUPID的代码可以让人更加愉悦地进行阅读和修改。事实上,CUPID中的五个特质并不是相互独立的,它们常常可以互相促进。

可组合并符合Unix风格的代码(做一件事,并把它做好)让人感觉就像是一个可靠的老朋友。符合惯例的代码让从未看过此代码的人也觉得非常熟悉。可预测的代码将我们从一系列“惊喜”中解脱出来。基于领域的代码减少了从需求到方案之间的认知距离。

在每次修改代码时,如果每个人都能将代码向这几个方向所指向的中心点靠近一点,那就可以让代码越来越好。

参考:

  • 关于Facade模式,可以参考这里
  • 关于迪米特法则,可以参考这里
  • 关于Unix哲学,可以参考Wiki

文/Thoughtworks 廖光明
原文链接:https://insights.thoughtworks.cn/good-code-five-qualities-cupid/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/379960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试3

一 路径 路径:写想要找到的资源位置。 1.相对路径:相对于当前html文件去找其他资源的方式 (1)同级查找:当前html和目标在同一级别目录中,语法:直接写资源的名字即可 (2&#xff09…

林心如常驻《向往的生活》,周杰却陷地域黑,做人的差别太大了吧

十年前如果有人提起周杰,就算是不能如雷贯耳,最起码也是妇孺皆知,毕竟那时候他太有名气了。因为拍摄《还珠格格》,让他和林心如等人一起爆红,不过此后的林心如,却很少再有优秀作品问世。 而周杰却不一样&am…

CCNP350-401学习笔记(易错题合集)

CCNP350-401学习笔记(1-50题)_殊彦_sy的博客-CSDN博客CCNP350-401学习笔记(2023.2.17)https://blog.csdn.net/shuyan1115/article/details/129088574?spm1001.2014.3001.5502CCNP350-401学习笔记(51-100题&#xff09…

Win10系统开始菜单无法点击解决方法分享

Win10系统开始菜单无法点击解决方法分享。有用户电脑一开机之后,就出现了开始菜单无法正常点击的情况。我们很多设置项都是通过开始菜单来进行开启的。那么这个功能无法点击了怎么办呢?接下来我们一起来看看以下的解决方法分享吧。 方法一: 1…

面试官:给你一段有问题的SQL,如何优化?

大家好,我是飘渺!我在面试的时候很喜欢问候选人这样一个问题:“你在项目中遇到过慢查询问题吗?你是怎么做SQL优化的?”很多时候,候选人会直接跟我说他们在编写SQL时会遵循的一些常用技巧,比如&a…

【力扣(LeetCode)】【MySQL】【185.部门工资前三高的所有员工】

学习时间: 2023年3月1日 题目描述: 题解分享: # 作 者 : 繁 华 倾 夏# 力扣(LeetCode):185. 部门工资前三高的所有员工# MySQL 数据库selectd.Name as Department, e1.Name as Employee, e1.Salary fromEmployee e1joinDepartment d on …

数据结构初阶 -- 顺序表

数据结构初阶 链表的讲解 目录 一. 线性表 1.1 定义 1.2 特点 二. 顺序表 2.1 定义 2.2 代码 2.3 功能需求 2.4 静态顺序表的特点以及缺点 2.5 动态的顺序表 2.6 动态顺序表接口的实现 三. 代码 头文件 主文件 一. 线性表 1.1 定义 线性表(linear li…

【算法】Tire字符串

作者:指针不指南吗 专栏:算法篇 🐾或许会很慢,但是不可以停下🐾 文章目录1.Trie的基本思想1.1什么是Trie1.2字符串条件1.3如何存储字符串1.4如何查找字符串2.Trie的代码实现2.1怎么用数组建树2.2完整代码1.Trie的基本思…

柔性电路板的优点、分类和发展方向

柔性电路板是pcb电路板的一种,又称为软板、柔性印刷电路板,主要是由柔性基材制作而成的一种具有高可靠性、高可挠性的印刷电路板,具有厚度薄、可弯曲、配线密度高、重量轻、灵活度高等特点,主要用在手机、电脑、数码相机、家用电器…

CSGO社区服搭建服务器架设游戏服务端教程

CSGO社区服搭建服务器架设游戏服务端教程 我是艾西,上一篇说了搭建CSGO服务器需要准备服务器以及安装好所需要的环境,那么今天说一下CSGO社区私人服务器怎么搭建游戏服务端 搭建CSGO服务器比较简单,Valve开发者社区wiki也给出了安装指导&…

仓库管理如何实现扫码出入库?

仓库实现扫描出入库管理具体应该怎么做?以下方参考模板为例,可以点击打开配合阅读: 参考模板:通用仓库管理(官方)① 库存卡设计: 此表单主要用于代替传统纸质库存卡的记录功能,货物…

【备战面试】每日10道面试题打卡-Day1

本篇总结的是Java基础知识相关的面试题,后续也会更新其他相关内容 文章目录1、JVM、JRE和JDK的关系?2、Java语言有哪些特点?3、Java和C的区别有哪些?4、Java有哪些数据类型?5、访问修饰符 public、private、protected&…

企业如何实现精细化人员管理?五大业务场景值得关注

近年来,随着大数据、人工智能和云计算等信息技术不断升级与渗透,处在数字化变革的劳动力密集型企业希望利用更加智能化的劳动力管理软件,帮助企业实现规范化的管理。 面对企业劳动力管理理念的变化,以及数字化转型的发展渗透&…

Puppeteer项目结构梳理

最近接触了一个个人感觉很奈斯的项目,故记录思路如下: puppeteer项目梳理: 入口文件 run.js 入口命令 node run.js YourConfig.json 1、我们可以在自己的config.json里面设置好 ①、登录的用户名密码;aws或其它服务器的access等id,accessKey…

二叉树的性质(概念/特性/存储结构)

目录1 二叉树的定义及主要特性1.1 二叉树的定义1.2 特殊二叉树1.2.1 满二叉树1.2.2 完全二叉树1.2.3 二叉排序树1.2.4 平衡二叉树1.3 二叉树的性质1.3.1 非空二叉树上的叶结点数1.3.2 非空二叉树第k层结点数1.3.3 高度为h的二叉树至多结点数1.3.4 完全二叉树结点与双亲的关系1.…

电子价格标签-系统结构

一、V2.4基站软件电子标签 接收PC下达的操作指令,解析后再通过RF发送给电子标签,接收路由器发送的数据信息并解析,更新数据。 1. 2.1寸电子价签 2. 2.9寸电子价签 ​ 3. 4.2寸电子价签 ​ 4. 7.5寸电子价签 ​ 5. 10.2寸电子价签 二、V4…

当审稿人回复这些审稿意见时,其实是在暗示你这些细节需要注意

当我们翘首以盼SCI论文投稿的审稿决定时。心情往往是复杂的,想收到审稿决定又担心收到的是不好的审稿决定。不管审稿结论如何,我们首先要明白的是那几个简短字母的审稿决定对我们来说,意味着什么。只有这样才有可能既不妄自菲薄也不杞人忧天。…

Intel CSME 简述

SME 算是 Intel X86 PC 上最神秘的部分了,本文根据 us-19-Hasarfaty-Behind-The-Scenes-Of-Intel-Security-And-Manageability-Engine 一文写成。讲述内容无法证伪,各位随便听听即可,了解这些能够帮助BIOS 工程师更好的理解一些操作的实现。文章基于 Intel 第八代第九代CPU(…

springCloud之seata

一、Seata是什么 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 官方文档 https://seata.io/zh-cn/docs/overview/wha…

发现新大陆——原来软件开发根本不需要会编码(看我10分钟应用上线)

目录 一、前言 二、官网基础功能及搭建 三、体验过程 01、连接数据源 02、设计表单 03、流程设计 04、图表呈现 05、组织架构设置 五、效率评价 六、小结 一、前言 众所周知,每家公司在发展过程中都需要构建大量的内部系统, 如运营使用的用户…