代码Review老被怼?这10个编程好习惯,让你写出同事都点赞的好代码!

嘿,兄弟姐妹们,我是老码小张。

不知道你有没有遇到过这样的场景:接手一个"祖传"项目,或者哪怕是自己几个月前写的代码,想加个小功能,或者改个 Bug,结果发现牵一发而动全身?改了一行代码,测试报出来十个八个新问题,越改越乱,最后心态崩了,默默地 git reset --hard,心里想着:"这代码谁写的,也太烂了吧!"(然后发现是自己写的...尴尬不?)

或者,每次提交代码 Code Review 的时候,总是被大佬们指出各种"坏味道":这里逻辑太复杂、那里职责不清晰、到处都是重复代码... 心里是不是有点小委屈,觉得"功能能跑不就行了吗?"

其实啊,写出能跑的代码只是第一步,写出高质量、易维护、易扩展的代码,才是咱们程序员进阶的必经之路。这不仅仅是为了让别人看着舒服,更是为了未来的自己少加班、少掉头发!

今天,我就用大白话跟你唠唠 10 个能显著提升代码质量的编程好习惯。掌握了它们,不说立马成为大神,至少能让你写的代码看起来更"专业",下次 Code Review 底气都足一些!

1. 各司其职,职责单一 (SRP - Single Responsibility Principle)

这可是 SOLID 原则里的老大。啥意思呢?简单说,就是一个类(或者一个函数、一个模块)最好只干一件具体的事儿,并且干好它。就像一把瑞士军刀,功能挺多,但真要拧个大螺丝,还是专门的螺丝刀好用。

如果违反了会怎样? 一个类干的事儿太多,就像一个万能员工,啥都管。结果就是:

  • 难修改:改动一个功能,可能影响到其他不相关的功能。
  • 难测试:职责混在一起,单元测试写起来贼费劲。
  • 难理解:代码臃肿,逻辑复杂,新人接手直接懵圈。

怎么做? 拆分!把不同的职责分离到不同的类或函数里。

python 复制代码
# 反例:一个类干太多事
class UserManager:
    def get_user_info(self, user_id):
        # ... 连接数据库,获取用户信息 ...
        print("获取用户信息")

    def export_user_to_excel(self, user_id):
        # ... 获取用户信息 ...
        # ... 生成 Excel 逻辑 ...
        print("导出用户到 Excel")

    def send_welcome_email(self, user_id):
        # ... 获取用户信息 ...
        # ... 构造邮件内容 ...
        # ... 发送邮件 ...
        print("发送欢迎邮件")

# 正例:拆分职责
class UserRepository:
    def get_user_info(self, user_id):
        # ... 只负责数据获取 ...
        print("从数据库获取用户信息")
        return {"id": user_id, "name": "老张"}

class UserExcelExporter:
    def export(self, user_info):
        # ... 只负责生成 Excel ...
        print(f"将用户信息 {user_info} 导出到 Excel")

class EmailService:
    def send_email(self, user_info, subject, body):
        # ... 只负责发送邮件 ...
        print(f"向 {user_info['name']} 发送邮件: {subject}")

# 使用时组合
repo = UserRepository()
exporter = UserExcelExporter()
mailer = EmailService()

user = repo.get_user_info(123)
exporter.export(user)
mailer.send_email(user, "欢迎", "欢迎加入!")

你看,拆分之后,每个类是不是清爽多了?想改导出逻辑,就去 UserExcelExporter;想换邮件服务商,就改 EmailService,互不干扰。

2. 拥抱变化,扩展开放,修改封闭 (OCP - Open/Closed Principle)

这也是 SOLID 里的老二。意思是说,你的软件实体(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。人话就是:加新功能的时候,尽量不要去改动已经测试过、稳定运行的老代码,而是通过增加新代码的方式来扩展。

怎么做到? 通常通过抽象(接口、抽象类)和多态来实现。定义好稳定的抽象层,新增的功能作为具体实现类来扩展。

想象一下支付场景,一开始只支持支付宝,后来要加微信支付、银联支付。如果直接在原来的支付函数里用 if-else 判断,那代码会越来越臃肿,每次加新的支付方式都要改老代码。

更好的方式是定义一个支付接口(PaymentProcessor),包含 pay() 方法。支付宝、微信支付分别实现这个接口。需要加新的支付方式?再加个新的实现类就行了,完全不用动原来的代码。

graph TD A[客户端 Client] --> B(支付接口 PaymentProcessor); B -- 实现 --> C[支付宝 AlipayProcessor]; B -- 实现 --> D[微信支付 WeChatProcessor]; B -- 实现 --> E[...银联 UnionPayProcessor 新增]; style B fill:#f9f,stroke:#333,stroke-width:2px

这样,对于支付方式的扩展(新增 E)是开放的,但对于支付接口 B 和已有的实现 C、D 是封闭的,不需要修改它们。

3. 里氏替换,子类得"争气" (LSP - Liskov Substitution Principle)

SOLID 老三。简单说,所有引用基类(父类)的地方,必须能够透明地使用其子类的对象,而不引发错误或改变程序的原有行为。 也就是说,子类应该能够完全替代它的父类,并且表现出和父类一致的行为(或者说是符合父类定义的契约)。

经典的反例就是"正方形是长方形"的问题。如果 Rectangle 类有 setWidthsetHeight 方法,而 Square 继承自 Rectangle,为了维持正方形的特性(边长相等),SquaresetWidth 可能需要同时修改 height。这就破坏了父类 Rectangle 的行为约定(设置宽度不应该影响高度),导致替换后行为不一致。

违反 LSP 会导致继承体系混乱,子类行为不可预测,给使用者带来困扰。设计继承关系时,要确保子类真正符合 "is-a" 的关系,并且遵循父类的行为约定。

4. 接口隔离,别强迫我实现用不上的 (ISP - Interface Segregation Principle)

SOLID 老四。意思是客户端不应该被强迫依赖它不使用的方法。 如果一个接口功能太多、太"胖",不同的客户端可能只需要其中的一部分功能,但却不得不依赖整个臃肿的接口。

怎么做? 把"胖"接口拆分成更小、更具体的接口。这样,客户端只需要依赖它真正关心的那些小接口。

比如,一个 Worker 接口有 work()eat() 方法。对于 HumanWorker 来说没问题,但对于 RobotWorker 呢?机器人不吃饭啊!强迫 RobotWorker 实现一个空的 eat() 方法就不太好。

更好的做法是拆成 Workable 接口(含 work())和 Eatable 接口(含 eat())。HumanWorker 实现两个接口,RobotWorker 只实现 Workable 接口。

graph TD subgraph "胖接口 (Bad)" IWorker["IWorker (work, eat)"] HW1[HumanWorker] --> IWorker RW1[RobotWorker] --> IWorker end subgraph "接口隔离 (Good)" IWorkable["IWorkable (work)"] IEatable["IEatable (eat)"] HW2[HumanWorker] --> IWorkable HW2 --> IEatable RW2[RobotWorker] --> IWorkable end style HW1 fill:#fbb,stroke:#f00 style RW1 fill:#fbb,stroke:#f00 style HW2 fill:#bfb,stroke:#0f0 style RW2 fill:#bfb,stroke:#0f0

这样,RobotWorker 就不用依赖它不需要的 eat() 方法了。

5. 依赖倒置,面向接口编程 (DIP - Dependency Inversion Principle)

SOLID 老五。这个原则有两点:

  • 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
  • 抽象不应该依赖于具体实现。具体实现应该依赖于抽象。

有点绕?核心思想就是:要依赖抽象(接口或抽象类),而不是依赖具体的实现类。

想象一下,你的业务逻辑(高层模块)直接依赖一个具体的 MySQL 数据库操作类(低层模块)。如果将来想换成 PostgreSQL 数据库,那不是得把所有用到 MySQL 操作的地方都改一遍?

应用 DIP,我们应该定义一个数据库操作的接口(比如 IDatabase),业务逻辑依赖这个接口。然后,我们提供 MySQLDatabasePostgreSQLDatabase 作为这个接口的具体实现。通过依赖注入(DI)等方式,在运行时决定具体使用哪个实现。

graph TD subgraph "不好的依赖 (Bad)" BL1[业务逻辑 BusinessLogic] --> MySQL[MySQLDatabase 具体类] end subgraph "依赖倒置 (Good)" BL2[业务逻辑 BusinessLogic] --> IDB(数据库接口 IDatabase) MySQLImpl[MySQLDatabase 实现] -- 实现 --> IDB PGSQLImpl[PostgreSQLDatabase 实现] -- 实现 --> IDB end style MySQL fill:#fbb,stroke:#f00 style IDB fill:#f9f,stroke:#333,stroke-width:2px style BL2 fill:#bfb,stroke:#0f0 style MySQLImpl fill:#bfb,stroke:#0f0 style PGSQLImpl fill:#bfb,stroke:#0f0

这样,高层(业务逻辑)和低层(具体数据库实现)都依赖于抽象(IDatabase),更换底层实现对高层几乎没有影响,代码更灵活、可测试性也更好。

(插句嘴,SOLID 这五个原则是面向对象设计的基石,理解透彻对写出高质量代码非常有帮助!)

6. 保持简单,傻瓜都能懂 (KISS - Keep It Simple, Stupid)

这个原则简直是真理!尽量让你的代码简单、直接、易于理解。 避免不必要的复杂性,不要为了炫技而写一些花里胡哨、难以读懂的代码。

简单的代码更容易:

  • 阅读和理解
  • 修改和维护
  • 测试和调试

下次当你写下一段自认为"聪明"但复杂的代码时,停下来想一想:有没有更简单直接的方法?几个月后的自己或者接手的同事能看懂吗?

7. 拒绝重复,复制代码是万恶之源 (DRY - Don't Repeat Yourself)

系统中每一处知识都应该有单一、无歧义、权威的表示。 说白了,就是不要写重复的代码!

如果你发现自己在不同的地方复制粘贴同样或非常相似的代码块,那就要警惕了。一旦这部分逻辑需要修改,你就得找到所有复制粘贴的地方去改,很容易遗漏,导致 Bug。

怎么做?

  • 把重复的逻辑提取成函数或方法。
  • 把重复用到的常量定义成常量或枚举。
  • 考虑使用类、继承、组合等方式来复用代码。
  • 配置信息抽取到配置文件中。
python 复制代码
# 反例:重复计算折扣
def calculate_order_price_vip(price, quantity):
    total = price * quantity
    discount = total * 0.1 # VIP 折扣
    final_price = total - discount
    print(f"VIP 订单最终价格: {final_price}")
    return final_price

def calculate_order_price_svip(price, quantity):
    total = price * quantity
    discount = total * 0.2 # SVIP 折扣
    final_price = total - discount
    print(f"SVIP 订单最终价格: {final_price}")
    return final_price

# 正例:提取计算逻辑,通过参数控制折扣
def calculate_order_price(price, quantity, discount_rate):
    total = price * quantity
    discount = total * discount_rate
    final_price = total - discount
    print(f"折扣率 {discount_rate*100}%, 最终价格: {final_price}")
    return final_price

calculate_order_price(100, 2, 0.1) # VIP
calculate_order_price(100, 2, 0.2) # SVIP

DRY 不仅仅是代码层面的,也适用于文档、测试、构建脚本等方方面面。

8. 你不需要它!别过度设计 (YAGNI - You Ain't Gonna Need It)

这是极限编程(XP)的一个原则。意思是:不要现在就去实现那些你猜你未来可能会用到的功能。只实现当前真正需要的功能。

程序员往往有"未雨绸缪"的倾向,喜欢设计一些"灵活"、"可扩展"的架构,添加一些现在用不到但"将来可能有用"的功能。但很多时候,"将来"永远不会来,或者需求变化了,当初的设计反而成了负担。

过度设计会:

  • 增加不必要的复杂性。
  • 浪费开发时间。
  • 引入潜在的 Bug。

遵循 YAGNI,可以让你聚焦于当前最重要的需求,更快地交付价值。当然,这不意味着完全不考虑扩展性,而是在简单设计过度设计之间找到平衡。

9. 组合优于继承,别滥用 "is-a" (Composition Over Inheritance)

这是面向对象设计中一个非常重要的建议。继承("is-a" 关系)是一种强耦合关系,子类和父类紧密绑定。父类的改变很容易影响到所有子类,而且 Java/C# 这类语言还不支持多重继承。

组合("has-a" 关系)则是将所需的功能作为成员变量(组件)包含进来,更加灵活。一个类可以通过组合不同的组件来实现不同的功能,运行时也可以动态地改变组合关系。

什么时候用继承? 当子类确实是父类的一个特殊类型(满足 LSP),并且想复用父类的实现时。

什么时候用组合? 当你需要复用功能,但类之间不是严格的 "is-a" 关系,或者你需要更灵活的组装方式时。

优劣势对比

特性 继承 (Inheritance) 组合 (Composition)
关系 is-a (强耦合) has-a (松耦合)
代码复用 复用父类实现 (白盒复用) 复用组件功能 (黑盒复用)
灵活性 较低,编译时确定 较高,可在运行时改变组合
对修改的封装 较差,父类修改可能破坏子类 较好,组件接口稳定,内部修改不影响使用者
多重继承 部分语言不支持,易导致"菱形问题" 容易实现类似多重继承的效果

大部分情况下,优先考虑组合,它能带来更松耦合、更灵活的设计。

10. 关注点分离,让代码清爽起来 (SoC - Separation of Concerns)

这是一个更宏观的设计原则。意思是将一个复杂的系统或程序,按照不同的关注点(功能、职责)分解成不同的部分,每个部分处理一个独立的关注点。

这和 SRP 有点像,但 SoC 通常用在更高的层次上,比如:

  • 分层架构:表现层(UI)、业务逻辑层(Service)、数据访问层(DAO/Repository)。每一层关注不同的事情。
  • MVC/MVP/MVVM:模型(Model)、视图(View)、控制器/表示器/视图模型(Controller/Presenter/ViewModel)分离。
  • 微服务架构:将大型单体应用拆分成多个独立的服务,每个服务关注特定的业务领域。
graph TD subgraph "典型三层架构 (SoC 示例)" UI[表现层 Presentation Layer] --> BLL[业务逻辑层 Business Logic Layer] BLL --> DAL[数据访问层 Data Access Layer] DAL --> DB[(数据库 Database)] end style UI fill:#ccf,stroke:#33f style BLL fill:#cfc,stroke:#3f3 style DAL fill:#fcc,stroke:#f33

关注点分离能让系统的不同部分解耦,提高模块化程度,使得系统更容易理解、开发、测试和维护。

光说不练假把式,怎么落地这些原则?

知道了这些原则,更重要的是在日常开发中去实践它们:

  1. Code Review 时刻提醒:无论是 Review 别人的代码还是自己的代码被 Review,都可以用这些原则作为参照。
  2. 小步重构:遇到"坏味道"代码时,不要害怕修改。利用这些原则,小步、安全地进行重构。写测试是安全重构的保障!
  3. 从新代码做起:写新功能、新项目时,有意识地运用这些原则来指导设计。
  4. 刻意练习:尝试用不同的原则去解决同一个问题,对比差异。
  5. 讨论与交流:和同事多讨论代码设计,互相学习。

记住,这些原则不是银弹,也不是死板的教条。理解其背后的思想,根据具体场景灵活运用,才能真正写出优雅、健壮的代码。

希望今天分享的这几个编程好习惯能对你有所启发。代码质量的提升是一个持续修炼的过程,共勉!


我是老码小张,一个搬砖多年,喜欢琢磨技术原理,热爱在实践中不断学习和成长的普通技术人。如果你觉得这篇文章对你有帮助,或者有什么想交流的,欢迎在评论区留言,一起进步!

相关推荐
懒懒是个程序员11 分钟前
layui时间范围
前端·javascript·layui
NoneCoder13 分钟前
HTML响应式网页设计与跨平台适配
前端·html
凯哥197016 分钟前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端
烛阴19 分钟前
面试必考!一招教你区分JavaScript静态函数和普通函数,快收藏!
前端·javascript
GetcharZp21 分钟前
xterm.js 终端神器到底有多强?用了才知道!
前端·后端·go
JiangJiang25 分钟前
🚀 React 弹窗还能这样写?手撸一个高质量 Modal 玩起来!
前端·javascript·react.js
吃炸鸡的前端39 分钟前
el-transfer穿梭框数据量过大的解决方案
前端·javascript
洞窝技术44 分钟前
MYSQL:关于索引你想知道的
后端·mysql
高德开放平台1 小时前
文末有奖|高德MCP 2.0 出行领域首发打通大模型与高德地图APP互联
前端
MrWho不迷糊1 小时前
企业级权限系统怎么设计四 —— ABAC模型统一功能权限与数据权限
后端·微服务