代码规范-对抗软件复杂度

news2025/2/28 15:50:09

1、为什么需要代码规范

任何系统性的项目都需要架构设计,而架构设计的核心命题是控制复杂度

但随着项目的不断迭代,复杂度就会不断上升,研发效率就会不断下降。
在这里插入图片描述

而代码规范正是对抗软件复杂度的有效手段,通过约定俗成的规则,降低复杂度,提升研发效能。

从团队角度来说,统一的代码规范有利于减少阅读成本和理解成本,并且能提高代码质量,长期来说,项目的稳定且可维护,也能更好更快速的支撑业务发展。

在这里插入图片描述

2、代码是怎么变坏的

2.1、重复的代码

同一个功能,在不同的业务域,大家用同一种技术甚至不同技术都去实现了一遍,撇开复用性不说,当我们要去修改底层的逻辑实现时,上层调用有一个漏改都是莫大的风险。

2.2、早期有效的决策不再有效

初期,我们有能力把一段代码写的简洁且逻辑清晰,但是当业务不断迭代,逻辑就变得越来越复杂,当其他同学来接手时,是改还是不改,改了出问题谁来背锅?这是一个灵魂的拷问,然后一边叹气一边新增自己的代码。。。

2.3、过早的优化

过早的优化是万恶之源,为什么这么说,百万和千万级别日活的架构肯定是不一样的,架构是需要演进的,如果一开始百万级别日活就想奔着千万级别的架构去,不仅脱离了实际的业务需求,还浪费大量的人力物力财力,反而得不偿失,从实际出发,适合自己的才是最重要的。

2.4、对合理性没有苛求

技术方案往往都不是单一的,当我们有「能跑就行」的想法时,就会选择最简单粗暴的方案,但是往往这种方案的复用性和扩展性都很差,当历史的浪潮不断向前推进,就会演变成技术负债,后人一边叹气一边又在屎山上拉了一泡。

2.5、过度设计

调用链路就像老奶奶的裹脚布一样,又臭又长,一层又一层,增加理解成本,降低开发效率。

2.6、没有设计

没有设计也相当可怕,一个函数动辄上千行,剪不断理还乱,然后后来者又是一边叹气一边。。。

2.7、排期紧

曾经看到这么一个问题,为什么大厂屎山也这么多,高赞的前两个回答是这么说的:

  1. 因为只允许有写一遍就成的时间
  2. 因为能用就行,需求都排不过来

进入一个死循环,屎山越堆越高。。

3、如何让代码变好

3.1、命名

大到项目名、模块名、包名、对外暴露的接口,小到类名、函数名、变量名、参数名,只要是做开发,我们就逃不过「起名字」这一关。命名的好坏,对于代码的可读性来说非常重要,甚至可以说是起决定性作用的。

3.1.1、命名多长最合适?

有两种情况,一种命名特别短,对于代码的编写者来说,自己对代码的逻辑很清楚,总感觉用什么样的命名都可以达意,实际上,对于不熟悉你代码的同事来讲,可能就不这么认为了。

另一种,命名很长,觉得命名一定要准确达意,哪怕长一点也没关系,但是,如果函数、变量的命名很长,那由它们组成的语句就会很长。在代码列长度有限制的情况下,就会经常出现一条语句被分割成两行的情况,这其实会影响代码可读性。

原则上,命名是以能准确达意为目标。多换位思考,以阅读者的视角去考量命名是否够直观。

3.1.2、命名要可读、可搜索

什么是命名可读,指的是不要用一些特别生僻、难发音的英文单词来命名,更不要随意造词。

虽然我们并不排斥一些独特的命名方式,但起码得让大部分人看一眼就能知道怎么读。而生僻、难发音的单词会严重影响交流沟通。

其次是可搜索,我们在IDE中编写代码的时候,经常会用「关键词联想」的方法来自动补全和搜索。比如,键入某个对象「.get」,希望IDE返回这个对象的所有get开头的方法。

3.1.3、行业规范

还有一些行业通用的规范:

  1. 接口前缀加「I」,表示一个Interface。比如IUserService,对应的实现类命名为UserService;
  2. 弹窗后缀加「Dialog」,表示一个Dialog。比如AppUpdateDialog;
  3. 工具类后缀加「Utils」,表示一个工具类。比如TrackUtils;
  4. 等等;

3.1.4、示例

bad:

fun getName(){}

good:

fun getUserName(){}

3.2、注释

命名很重要,注释跟命名同等重要。

3.2.1、注释应该写什么?

注释的目的就是让代码更容易看懂。只要符合这个要求的内容,你就可以将它写到注释里。

比如,阐述代码的逻辑,你为什么这么做,想要达到什么样的效果等等。

3.2.2、注释是不是越多越好?

注释太多和太少都有问题。

太多,有可能意味着代码写得不够可读,需要写很多注释来补充。除此之外,注释太多也会对代码本身的阅读起到干扰。而且,后期的维护成本也比较高,有时候代码改了,注释忘了同步修改,就会让代码阅读者更加迷惑。

当然,如果代码中一行注释都没有,那只能说明这个程序员很懒,我们要适当督促一下,让他注意添加一些必要的注释。

3.2.3、注释类别

在这里插入图片描述

3.2.4、示例

bad:

    /**
     * 取消
     */
    protected fun cancelJob(job: Job?) {
        if (job != null && job.isActive && !job.isCompleted && !job.isCancelled) {
            job.cancel()
        }
    }

good:

    /**
     * 取消协程 会抛出CancellationException
     * @param job 协程job
     */
    protected fun cancelJob(job: Job?) {
        if (job != null && job.isActive && !job.isCompleted && !job.isCancelled) {
            job.cancel()
        }
    }

3.3、代码风格

3.3.1、函数、类多大才合适?

函数的代码行数不要超过一屏幕的大小,比如50行。

3.3.2、一行代码多长最合适?

最好不要超过IDE的显示宽度。当然,也不能太小,否则会导致很多稍微长点的语句被折成两行,也会影响到代码的整洁,不利于阅读。

3.3.3、善用空行分割单元块

对于比较长的函数,为了让逻辑更加清晰,可以使用空行来分割各个代码块。

除此之外,在类的成员变量与函数之间、静态成员变量与普通成员变量之间、各函数之间、甚至各成员变量之间,我们都可以通过添加空行的方式,让这些不同模块的代码之间,界限更加明确。写代码就类似写文章,善于应用空行,可以让代码的整体结构看起来更加有清晰、有条理。

3.3.4、格式化

使用统一的格式化规则,比如空格、换行等,格式化规则不统一,容易引起不必要的变更,不利于代码评审和历史变更查询。

3.3.5、示例

bad:

AlertDialog.Builder(context).setView(0).setTitle(R.string.dialog_title).setMessage(R.string.dialog_message).setIcon(0) .create()

good:

AlertDialog.Builder(context)
            .setView(0)
            .setTitle(R.string.dialog_title)
            .setMessage(R.string.dialog_message)
            .setIcon(0)
            .create()

3.4、编码技巧

3.4.1、把代码分割成更小的单元块

大部分人阅读代码的习惯都是,先看整体再看细节。所以,我们要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节,让阅读代码的人不至于迷失在细节中,这样能极大地提高代码的可读性。不过,只有代码逻辑比较复杂的时候,我们其实才建议提炼类或者函数。毕竟如果提炼出的函数只包含两三行代码,在阅读代码的时候,还得跳过去看一下,这样反倒增加了阅读成本。

3.4.2、避免函数参数过多

我个人觉得,函数包含3、4个参数的时候还是能接受的,大于等于5个的时候,我们就觉得参数有点过多了,会影响到代码的可读性,使用起来也不方便。

一般有2种处理方法:

  1. 考虑函数是否职责单一,是否能通过拆分成多个函数的方式来减少参数。
  2. 将函数的参数封装成对象。

3.4.3、函数设计要职责单一

我们在前面讲到单一职责原则的时候,针对的是类、模块这样的应用对象。实际上,对于函数的设计来说,更要满足单一职责原则。相对于类和模块,函数的粒度比较小,代码行数少,所以在应用单一职责原则的时候,没有像应用到类或者模块那样模棱两可,能多单一就多单一。

整洁的代码只做好一件事,干脆利落,直接了当,易于阅读,易于维护。

3.4.4、移除过深的嵌套层级

代码嵌套层级过深往往是因为if-else、switch-case、for循环过度嵌套导致的。我个人建议,嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套。过深的嵌套本身理解起来就比较费劲,除此之外,嵌套过深很容易因为代码多次缩进,导致嵌套内部的语句超过一行的长度而折成两行,影响代码的整洁。

针对层级嵌套过深的代码可以使用多态简化逻辑,移除不必要的if或else,也可以使用策略模式,提前return退出嵌套等。

3.4.5、少即是多

无形装逼最为致命:

  1. 过度设计:有的同学为了炫技,各种设计模式咔咔往上整,反而增加复杂度;
  2. 隐式耦合:这种是想炫技但功力不够的,设计的不够优雅反而留下后遗症;

建筑师米斯.凡德洛曾说过,less is more,提倡简单,反对度装饰的设计理念。简单的东西往往带给人们的是更多的享受。

4、客户端的技术栈

上面介绍了一些通用的规范,然而时代在变化,技术在演进,客户端的技术更新也是日新月异,因此也需要针对不同的技术栈有系统性的规约及代码风格。

比如:

  1. Java的文件名遵循驼峰命名法,而在Flutter中文件名使用下划线隔开;
  2. Java和OC是强类型语言,Swift和Kotlin是弱类型语言,不仅有类型推导上的区别,还有一些语法糖的特性;
  3. 等等;

下面是端上现有的一些技术栈:

语言规范文档
AndroidJavaJava开发手册(嵩山版)Google Java Style Guide
AndroidKotlinKotlin Coding conventions
iOSObjective-CCoding Guidelines for Cocoa
iOSSwiftSwift Style Guide
跨端FlutterEffective Dart
跨端动态化xxx(json -> TypeScript)Google TypeScript Style Guide
其他配置文件(xml、yml,json)Google XML Document Format Style Guide
其他脚本、插件(Python、Shell、Groovy)Shell Style GuidePython Style Guide

google开源规约:https://google.github.io/styleguide/

5、治理手段

5.1、检测工具

通过一些类似Lint之类的检测工具,在编写阶段通过警告,把不符合规范的代码扼杀在摇篮里。

Alibaba Java Coding Guidelines

5.2、代码评审

代码评审也称code review,俗称cr,cr的意义在于,作为局中人,尽管我们在编写代码的时候小心翼翼,但也可能会在无意间犯下一个小错误,而此时cr的人作为旁观者,可有一语点醒梦中人的效果,而且从实际问题出发产生思想的碰撞,相互学习,也有利于提升团队整体的编码水平。

5.3、扫码行动

我们端上正在做一些代码的治理,删除无用代码,下线老代码,比如原有的灰度校验在全量之后理应下线老的逻辑代码。

5.4、代码重构

我们也正在做模块化的重构治理,把以前设计不合理或者不满足现状诉求的地方做改进和优化。

6、一点思考

治理行动我们可以一年来一次,但这很明显不是最好的解决办法,代码是人写的,工具是辅助是底线,如何长治久安,还是要从根源上着手下功夫,这就需要我们团结一致,从思想上认可,从行动上落实,认真做好code review,始终对代码保持敬畏,对自己的代码负责,做一个有信仰有追求的程序员。

7、相关书籍

  • 人月神话
  • 代码整洁之道
  • 架构整洁之道
  • 编程珠玑
  • 重构·改善既有代码的设计
  • 设计模式之美

8、参考文档

  • 腾讯工程师,万字长文说 Code Review
  • 如何编写垃圾代码
  • 设计模式之美
  • 对抗软件复杂度的战争

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

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

相关文章

[附源码]计算机毕业设计JAVA户籍管理系统

[附源码]计算机毕业设计JAVA户籍管理系统 项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis M…

docker安装redis详细教程

1、下载最新redis镜像 docker pull redis //表示拉取最新的镜像 如果要指定版本 docker pull redis:latest//表示拉取最新的镜像 2、创建redis映射目录 mkdir /redisData/redis/conf 配置文件挂载在我指定的redisData/redis/conf/ 文件夹中,方便后续的修改 创建re…

Linux上单机部署RocketMq

Linux上单机部署RocketMq1、安装jdk2、下载rocketmq并解压3、创建日志文件夹4、启动namesrv5、启动broker6、查看和关闭7、rocketmq控制台7.1、控制台idea启动7.2、控制台jar包启动1、安装jdk rocketmq的运行是建立在jdk之上的,所以,我们要搭建rocketmq服…

uView u-slider 自定义滑块

有个需求UI设计的滑动选择器中的滑块如下所示: 项目中集成的是vView2.0组件库,u-slider组件中有 blockStyle 属性,看着是用来设置自定义滑块的。但是试了下,没有效果,不知怎么回事。看了一下uView1.0组件库 u-slider组…

大学生想做兼职应该怎么找,适合大学生的线上线下靠谱兼职推荐

大学生现在有很多兼职工作可以在网上和实体上做。他们可以根据个人能力和喜好进行选择。以下是一些低门槛的在线和离线兼职工作,希望能帮助到你。 线下兼职 1.勤工助学岗位 学校:通过学校提供的勤工俭学岗位,如办公室助理、图书馆助理等&am…

Java 线上机器 CPU 100% 的一次排查过程

文章目录1. 问题发生2. 数据库连接关闭问题排查3. 问题的进一步排查4. 解决方法1. 问题发生 日常敲代码突然收到生产环境异常告警,线上有一台机器 CPU 使用率飙升到 100 触发扩容,工作群里一下子鸡飞狗跳。 出现问题,首先当然是查看监控和日…

如何画架构图?

平时做过一些系统设计,也写过一些系统分析文章,从组件、关系、交互等方面提供一些建议,并用我之前写文章画的一些图举些例子。构成系统的组件通过形状、颜色、名称来逼近其概念。LevelDB 主要构件如上面 LevelDB 的架构图,包含的主…

Redis哨兵(Sentinel)

# Redis哨兵(Sentinel) Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务: 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常…

内核参数 sched_min_granularity_ns 为什么看不到啦?

linux内核从版本v5.13-rc1起(含),sysctl 已无法设置 kernel.sched_min_granularity_ns。 其实不止sched_min_granularity_ns,在 /proc/sys/kernel 下,和CPU调度相关的6个参数都不见了: sched_latency_ns …

Unity VR开发教程 OpenXR+XR Interaction Toolkit 2.1.1(七)射线抓取

文章目录📕教程说明📕添加射线功能的相关组件📕设置 Interaction Layer Mask📕让 XR Direct Interactor 不对 XR Ray Interactor 产生干扰📕使抓取的物体不会吸到手上📕远距离抓取时通过摇杆改变抓取物体的…

Node.js安装及环境配置

Node.js安装及环境配置1.下载安装Node.js2.npm安装路径配置3.环境变量配置4.换源5.测试npm安装1.下载安装Node.js Node.js官网 下载如图所示版本:(请根据自己的系统环境选择) 下载完成后傻瓜式安装即可 测试环境: PS C:\Users…

终于拿到了爆火全网的进一线大厂程序员必看的1700道java面试题

爆火全网的进一线大厂程序员必看的1700道java面试题到底有多牛? 牛不牛不敢说,但是有好多程序员是靠这一套1700道高频面试题,顺利收到很多大厂offer! 以至于,到现在为止,大厂都开始按照这一套1700道面试题…

java计算机毕业设计ssm美食视频教学网站element 前后端分离

项目介绍 高校实验室信息管理平台是使用JAVA的SSM技术,MySQL作为数据库开发,用户通过查看实验室信息,在线预约实验室,实现高校实验室信息化管理。首先对本论文进行分析后,提出平台的相关技术,然后整理系统的需求分析,根据需求进行功能和数据库设计,最后进行系统实现和测试 。 …

Redis的Java客户端

目录 1 前言 2 Jedis客户端 2.1 jedis快速入门 2.2Jedis连接池 3 SpringDataRedis 3.1快速入门 3.2ReisTemplate配置序列化工具 3.3 StringRedisTemplate 1 前言 在Redis官网中提供了各种语言的客户端,地址:https://redis.io/resources/clien…

螺栓防松设计

常用的防松方法有三种:摩擦防松、机械防松和永久防松。机械防松和摩擦防松称为可拆卸防松,而永久防松称为不可拆卸防松。常用的永久防松有:点焊、铆接、粘合等,这种方法在拆卸时大多要破坏螺纹紧固件,无法重复使用。常…

系统测试-从研发到测试过程

系统测试是为了发现错误而执行程序的过程,成功的测试是发现了至今尚未发现的错误的测试。目的是在真实系统工作环境下通过与系统的需求定义作比较,检验完整的软件配置项能否和系统正确连接,发现软件与系统/子系统设计文档和软件开发合同规定不…

[Redis] Redis实战--EVAL

✨✨个人主页:沫洺的主页 📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏 📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专…

基于移动品台的产品追溯系统设计与实现

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下,你想解决的问…

骚戴独家笔试---算法篇

链表 反转链表 /* public class ListNode { int val; ListNode next null;ListNode(int val) { this.val val; } }*/ import java.util.Stack; public class Solution {public ListNode ReverseList(ListNode head) {Stack<ListNode> stack new Stack<>();//把…

进程间通信:无名管道+有名管道

进程间通信&#xff08;Inter-Process Communication&#xff09; 为什么需要进程间通信 当程序是多进程协同工作时&#xff0c;进程间基本都会涉及到数据共享 如何实现进程间数据的共享? 使用进程间通信来实现数据共享 进程间有时需要传递消息 --但是进程在系统有自己的地址…