代码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. 讨论与交流:和同事多讨论代码设计,互相学习。

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

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


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

相关推荐
Uopiasd1234oo6 小时前
MetaFormer架构改进YOLOv26自适应稀疏注意力与卷积门控双重突破
yolo·架构
easy_coder6 小时前
Agent:原理、架构与工程实践(中篇)
架构·云计算
2601_949817726 小时前
Spring Boot3.3.X整合Mybatis-Plus
spring boot·后端·mybatis
uNke DEPH7 小时前
Spring Boot的项目结构
java·spring boot·后端
zhenxin01227 小时前
Spring Boot 3.x 系列【3】Spring Initializr快速创建Spring Boot项目
spring boot·后端·spring
码以致用7 小时前
DeerFlow Memory架构
人工智能·ai·架构·agent
超级无敌暴龙兽7 小时前
和我一起刷面试题呀
前端·面试
wzl202612137 小时前
企业微信定时群发技术实现与实操指南(原生接口+工具落地)
java·运维·前端·企业微信
小码哥_常7 小时前
Robots.txt:互联网爬虫世界的“隐形规则”
前端
2603_954708318 小时前
如何确保微电网标准化架构设计流程的完整性?
网络·人工智能·物联网·架构·系统架构