十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

干净代码实战:从命名、函数、注释到结构的核心习惯

干净代码实战:从命名、函数、注释到结构的核心习惯 1. 项目概述为什么“干净代码”不是架构师的专利干了十几年开发带过不少项目也看过无数代码。我发现一个挺有意思的现象一提到“干净代码”很多一线开发兄弟就觉得这是架构师或者技术专家才需要考虑的高级话题跟自己日常搬砖关系不大。要么觉得太虚要么觉得学起来门槛太高得先啃几本大部头的设计模式才行。其实这完全是个误解。“干净代码”的本质不是什么高深莫测的玄学而是一套能让你写代码时更舒服、更高效、更少出错的工作习惯。它不要求你立刻精通领域驱动设计或者微服务架构而是从你每天都要面对的命名、函数、注释、结构这些最基础的“零件”入手。就像你收拾工位不需要先学室内设计只需要养成“东西用完归位”、“文件分类存放”的习惯桌面自然就干净了。代码也一样。我见过太多项目初期功能跑得飞快但三个月后加个小需求就像在豆腐渣工程上盖楼战战兢兢bug频出。追根溯源往往不是架构设计得不好而是最基础的代码“卫生”没搞好。变量名是a、b、c函数长得能滚动三屏注释要么没有要么是过时的“谎言”。这样的代码谁来维护都头疼。所以今天我想聊的就是一套能立刻上手、无需前置架构知识的“干净代码”实操习惯。我们聚焦四个最核心、也最常被忽视的领域命名、函数、注释、结构。我会结合那些热搜词里大家真实遇到的困惑比如“字段注释怎么写”、“函数怎么设计”、“结构体怎么用”给出非常具体、能直接抄作业的方法。目标很简单让你明天写的代码就比今天更干净、更健壮一点。2. 核心习惯一让名字自己说话——命名规范实战命名可能是程序员最常做却也最随意的一件事。好的命名本身就是最好的注释它能极大地降低阅读和理解代码的心智负担。我们分几个场景来拆解。2.1 变量与函数命名从意图出发而非类型热搜里出现了skinhueslider,skinsatslider,skinbrightslider这样的命名。这是一个不错的起点因为它清晰地表明了这是控制“皮肤”颜色的“滑块”。但我们可以做得更好。坏味道示例// 模糊只说了类型 int a; float b; void process(); // 冗余信息 string nameString; // name 本身就是字符串String 多余 UserClass userClassObject; // User 已经足够干净代码实践使用有意义的名称名称应揭示其用途或包含的数据。skinhueslider-skinHueSlider(驼峰命名法更通用)但更进一步在UI上下文中或许直接叫hueSlider而其所属的Panel或Group命名为SkinColorPanel这样层次更清晰。从热搜“颜色控制滑块”延伸与其用slider1,slider2,slider3不如用hueSlider,saturationSlider,brightnessSlider一眼就知道各自控制什么。避免误导别让名字撒谎。如果你的集合不是列表就别叫accountList叫accounts或accountGroup。布尔变量用is,has,can开头如isValid,hasPermission。使用可读的名称宁可名字长一点也要清晰。genymdhms(生成日期年月日时分秒) 远不如generationTimestamp来得明白。现代IDE都有自动补全不要怕长名字。函数名用动词或动词短语函数是做事的名字应该说明它做什么。getUserData()-fetchUserProfile()(更精确)process()-calculateOrderTotal()或validateInput()(明确动作和对象)实操心得命名时在心里问自己两个问题“这个变量代表什么”、“这个函数要完成什么任务”。如果不能用一两句话回答说明名字没起好。对于像“人工智能命名史”这种宏观话题我们管不了但对于自己代码里的data,info,temp我们绝对有掌控权。2.2 避免命名冲突作用域与模块化的艺术热搜中提到了“mutation中使用state怎么避免命名冲突”这直指Vuex/Redux等状态管理库中的一个常见痛点。同样在普通代码中命名冲突也让人头疼。问题场景你在一个大型Vue2项目的store模块里有一个user模块的state里有个list字段另一个product模块的state里也有个list字段。在mutation或action里直接操作state.list就容易混淆。解决方案使用命名空间 (Namespacing)这是最根本的解决方法。Vuex允许你通过namespaced: true将模块隔离。访问时就需要带上路径commit(user/updateList, payload)或dispatch(product/fetchList)。这样list在各自的命名空间下互不干扰。这就像把文件分到不同的文件夹里。在模块内部使用具名常量为mutation types、action names定义常量并统一从模块文件导出。即使不使用命名空间清晰的常量名也能减少错误。// store/modules/user.js export const USER_UPDATE_LIST USER_UPDATE_LIST; export const USER_FETCH_LIST USER_FETCH_LIST; // 然后在mutation中使用 [USER_UPDATE_LIST]利用ES6模块的局部作用域对于普通的工具函数或组件善用import/export。将功能内聚在一个文件内只暴露必要的接口内部变量不会污染全局。注意事项不要过度依赖全局变量。热搜里那些“无法将...识别为cmdlet”的错误如opencode,claude,npm,git,pip很多情况是因为命令不在系统PATH环境变量中这本质也是一个“全局命名空间”污染和查找路径的问题。在代码中缩小变量的作用域使用局部变量而非全局变量是避免冲突的金科玉律。3. 核心习惯二函数是代码的积木——短小精悍只做一事函数是构建程序逻辑的核心单元。一个混乱的函数是代码腐化的开始。干净的函数遵循“单一职责原则”但这个原则怎么落地3.1 函数的长度与抽象层级一个最直观的指标长度。虽然不绝对但超过20行甚至15行的函数就值得你停下来审视一下。热搜中“python中class函数的用法”和“c语言fscanf和fprintf函数”都是基础但我们要关注的是如何用好它们。干净函数的特征短小理想情况下一个函数应该只做一件事并且做好。这件事应该小到可以用一个简短的句子描述清楚。缩进层级少函数里的if/else、while、for嵌套最好不超过两层。深层嵌套是“箭头代码”极其难读。参数要少最理想的参数数量是0个零参数函数其次是1个再次是2个。3个参数就需要充足理由了超过3个就应该考虑封装成对象结构体或类传入。看看fscanf(FILE *stream, const char *format, ...)它参数多是因为其功能决定的但我们自己写的业务函数应尽量避免。重构示例假设有一个处理订单的函数它验证订单、计算价格、扣减库存、生成日志。# 坏味道一个函数做所有事 def process_order(order_data, user_id, inventory_conn, logger): # 验证逻辑... (20行) if not valid: return False # 计算价格逻辑... (15行) # 扣减库存逻辑... (25行包含复杂的事务处理) # 记录日志逻辑... (10行) return True# 干净版本每个函数职责单一 def process_order(order_data, user_id, inventory_conn, logger): if not _validate_order(order_data, user_id): return False total_price _calculate_price(order_data) if not _update_inventory(order_data, inventory_conn): logger.error(库存更新失败) return False _log_order_activity(order_data, user_id, total_price, logger) return True # 每个辅助函数都很短小只做一件事 def _validate_order(order_data, user_id): ... def _calculate_price(order_data): ... def _update_inventory(order_data, conn): ... def _log_order_activity(order_data, user_id, price, logger): ...这样主函数process_order就像一个“导演”只负责调度具体活儿由各个“演员”小函数完成。每个小函数都易于理解、测试和复用。3.2 函数副作用与命令查询分离函数应该要么做什么事命令要么回答什么事查询但最好不要同时做两件。这就是“命令查询分离”CQS。查询函数获取数据不应该修改系统状态。例如getUserBalance(userId)它只返回余额不改变任何东西。命令函数修改系统状态但通常不返回值或只返回操作成功与否。例如withdrawMoney(userId, amount)它扣钱返回是否成功。混合两者会导致意想不到的副作用。例如一个叫saveAndGetCount()的函数既保存了数据又返回了总数调用者如果只关心总数而多次调用就会导致数据被重复保存。实操要点在设计和命名函数时就明确它的目的。是“做”还是“问”“做”的函数用动词开头save,delete,update“问”的函数用get、find、is、has开头。这能极大提高代码的可预测性。4. 核心习惯三注释是必要的“恶”——写对而非写多注释是个争议话题。干净代码倡导“代码即文档”意思是代码本身应该清晰到不需要注释。但这在现实中是理想状态。注释是必要的补充但必须是高质量的补充。热搜里大量关于注释的问题“字段注释”、“包注释”、“idea新建类默认注释”、“批量注释”恰恰说明了大家对注释的困惑。4.1 什么该注释什么不该注释不该注释的坏注释废话注释注释只是重复代码字面意思。i; // i 增加 1过时的注释代码改了注释没改。这是最危险的“谎言”。日志式注释在文件开头记录每次修改的日期和人。这应该由版本控制系统Git来管理。位置标记如/////////// PUBLIC METHODS ///////////。如果代码需要这种标记来区分说明你的类太大了该拆分了。注释掉的代码直接删掉Git会帮你记住。保留它们只会干扰阅读。应该注释的好注释法律信息版权声明、许可证必须。对意图的解释解释“为什么”这么做尤其是当代码本身看起来有点奇怪或违反直觉时。这是注释最大的价值。# 使用快速排序而非内置sort因为需要稳定排序特性来处理自定义对象 def sort_custom_items(items): quick_sort(items, keylambda x: x.custom_field)警示说明某些代码的后果或限制。// 注意此API调用有频率限制每秒最多10次 async function fetchDataFromExternalAPI() { ... }TODO/FIXME注释标明临时的、未来需要改进的地方。但要定期处理它们别让TODO列表无限增长。公共API文档对于类、库、模块的公开接口使用规范的文档注释如Javadoc, Docstring, JSDoc说明用途、参数、返回值、异常。这是给使用者的说明书。4.2 注释格式与工具实践字段/属性注释对于类中的重要字段特别是公共的或含义不直观的应该注释其用途和约束。/** * 用户邮箱地址用于登录和接收通知。必须符合邮箱格式。 */ private String email;IDE模板像“idea新建类默认注释”这类需求可以配置Live Template。但模板内容应简洁有用包含作者、创建日期可选、类简要说明即可避免信息冗余。批量注释使用IDE的快捷键如Ctrl/或Cmd/进行行注释/块注释而不是手动输入//或/* */。对于“notepad批量注释#”在Notepad中可以用列编辑模式或正则替换实现但这更提醒我们使用专业的代码编辑器或IDE能极大提升效率。核心原则努力让代码自解释。当你觉得需要写注释时先想想能否通过重命名变量、拆分函数、调整结构来让代码更清晰。如果不行再写下解释“为什么”的注释。5. 核心习惯四结构是代码的骨架——清晰胜于巧妙代码结构决定了它的可扩展性和可维护性。这里说的“结构”不单指“结构体”struct而是代码的组织方式包括文件、目录、类、模块之间的关系。热搜词里的“结构体”、“mysql数据库修改结构”、“计算机组成与结构”都指向了“组织”这个概念。5.1 数据结构的合理使用结构体或类是组织相关数据的利器。它能将分散的变量聚合成一个有意义的整体。反面教材// 传递一堆松散相关的参数 void printStudentInfo(char* name, int age, float score, char* major);正面教材typedef struct { char name[50]; int age; float score; char major[30]; } Student; void printStudentInfo(Student stu);使用Student结构体数据之间的关系一目了然函数接口也变得简洁。在面向对象语言中这自然就是类的属性。热搜中“c语言结构体链表基本语法”是这种思想的延伸用结构体表示节点再用指针链接起来形成更复杂的数据结构。关于数据库结构修改“mysql数据库修改结构”这属于架构层面的“结构”。修改生产数据库结构如加字段、改类型必须谨慎。干净代码的习惯体现在任何结构变更都应有对应的、版本化的迁移脚本Migration Script并且要在测试环境充分验证后才能上线。随意通过GUI工具直接修改生产库是灾难的开始。5.2 目录与文件组织分而治之项目初期所有代码堆在一个文件里可能还行。但稍微复杂点就必须分拆。按功能/模块划分这是最常见的方式。例如一个电商项目可以有user/,product/,order/,payment/等目录。每个目录下包含该模块的控制器、服务、数据模型等。按层级/角色划分例如controllers/,services/,models/,utils/。这种方式在清晰的同时也可能导致跨模块的关联代码分散。混合方式对于大型项目通常采用混合。顶层按功能模块分模块内再按层级分。关键点无论采用哪种一致性最重要。团队内要有统一的约定。一个utils文件夹放全局工具一个constants文件放全局常量避免散落各处。这能有效解决“vue2 store相关的js文件”如何组织的问题——通常将store模块按功能拆分到不同的js文件中然后在主index.js中组合。5.3 依赖管理与解耦结构清晰的另一个表现是依赖关系干净。高层模块不应依赖低层模块的细节两者都应依赖于抽象接口。这听起来像设计模式但其实有简单的实践避免循环依赖A文件导入BB文件又导入A。这会导致编译或运行时问题也说明职责划分不清。需要通过提取公共部分到第三个文件或使用依赖注入等方式解决。单向依赖让依赖关系像水流一样有清晰的方向。例如View层依赖ViewModel或ControllerController依赖ServiceService依赖Repository。不要出现Service回头去调用View的方法。使用接口或抽象类定义契约这在“虚函数”、“回调函数”等概念中体现。定义好接口具体的实现可以灵活替换这大大提高了代码的可测试性和灵活性。6. 实操过程从混乱到清晰的代码重构演练让我们用一个综合例子把命名、函数、注释、结构四个习惯串起来。假设我们有一段处理用户订单折扣的原始代码用Python示例原理通用原始代码混乱版本def calc(o, u): 计算订单折扣 o: 订单 u: 用户 d 0 # 检查用户等级 if u[lvl] VIP: d 0.1 # VIP打9折 # 检查商品类型 for i in o[items]: if i[type] ELECTRONICS: d max(d, 0.05) # 电子产品额外95折取最大折扣 # 检查促销时间 import datetime now datetime.datetime.now() if now.month 11 and now.day 10 and now.day 26: # 双十一期间 d d 0.1 # 叠加折扣 if d 0.3: d 0.3 # 最高30% off # 计算最终价格 total sum([it[price] * it[qty] for it in o[items]]) final_price total * (1 - d) o[final_price] final_price o[discount] d return o这段代码的问题命名随意o,u,d函数冗长且做了多件事计算折扣、应用规则、更新订单注释简陋且有时间硬编码逻辑嵌套魔法数字0.1, 0.05, 0.3满天飞。重构步骤改善命名习惯一calc-apply_discount_to_ordero-orderu-userd-discount_ratei-itemlvl-level提炼函数习惯二识别并拆分独立逻辑。计算基础用户折扣_get_user_discount计算商品类别折扣_get_product_category_discount检查是否在促销期_is_in_promotion_period计算促销叠加折扣_get_promotion_discount应用折扣并更新订单_apply_discount_and_update消除魔法数字使用常量习惯四class DiscountConfig: VIP_DISCOUNT 0.10 ELECTRONICS_DISCOUNT 0.05 PROMOTION_EXTRA_DISCOUNT 0.10 MAX_DISCOUNT 0.30 PROMOTION_MONTH 11 PROMOTION_START_DAY 10 PROMOTION_END_DAY 26优化结构引入数据对象习惯四使用dataclass或简单类来定义Order和User代替字典使字段明确。from dataclasses import dataclass from typing import List dataclass class Item: type: str price: float quantity: int dataclass class Order: items: List[Item] final_price: float 0.0 discount_rate: float 0.0 dataclass class User: level: str添加必要注释习惯三解释为什么折扣取最大值为什么有上限等业务规则。重构后代码干净版本from dataclasses import dataclass from typing import List import datetime # 折扣配置常量集中管理易于修改 class DiscountConfig: VIP_DISCOUNT 0.10 ELECTRONICS_DISCOUNT 0.05 PROMOTION_EXTRA_DISCOUNT 0.10 MAX_DISCOUNT 0.30 PROMOTION_MONTH 11 PROMOTION_START_DAY 10 PROMOTION_END_DAY 26 dataclass class Item: 订单项 type: str price: float quantity: int dataclass class Order: 订单 items: List[Item] final_price: float 0.0 discount_rate: float 0.0 dataclass class User: 用户 level: str # e.g., NORMAL, VIP def apply_discount_to_order(order: Order, user: User) - Order: 应用所有符合条件的折扣到订单上。 折扣规则用户折扣与商品折扣取最大值再与促销折扣叠加总折扣有上限。 discount_rate 0.0 # 1. 获取基于用户等级的折扣 user_discount _get_user_discount(user) # 2. 获取基于商品类别的折扣取所有商品中最大的类别折扣 category_discount _get_product_category_discount(order.items) # 基础折扣取两者最大值 base_discount max(user_discount, category_discount) # 3. 如果处于促销期叠加促销折扣 if _is_in_promotion_period(): promotion_discount _get_promotion_discount() # 折扣叠加但不超过上限 total_discount base_discount promotion_discount discount_rate min(total_discount, DiscountConfig.MAX_DISCOUNT) else: discount_rate base_discount # 4. 应用折扣并更新订单信息 return _apply_discount_and_update(order, discount_rate) # --- 以下为内部辅助函数职责单一 --- def _get_user_discount(user: User) - float: 根据用户等级返回折扣率 if user.level VIP: return DiscountConfig.VIP_DISCOUNT return 0.0 def _get_product_category_discount(items: List[Item]) - float: 遍历订单项返回适用的最大商品类别折扣率 max_discount 0.0 for item in items: if item.type ELECTRONICS: # 电子产品折扣可能比当前最大折扣更高 max_discount max(max_discount, DiscountConfig.ELECTRONICS_DISCOUNT) return max_discount def _is_in_promotion_period() - bool: 判断当前是否在预设的促销时间范围内 now datetime.datetime.now() return (now.month DiscountConfig.PROMOTION_MONTH and DiscountConfig.PROMOTION_START_DAY now.day DiscountConfig.PROMOTION_END_DAY) def _get_promotion_discount() - float: 返回促销期间的额外折扣率 return DiscountConfig.PROMOTION_EXTRA_DISCOUNT def _apply_discount_and_update(order: Order, discount_rate: float) - Order: 计算折后总价并更新订单对象 total_before_discount sum(item.price * item.quantity for item in order.items) order.final_price total_before_discount * (1 - discount_rate) order.discount_rate discount_rate return order可以看到重构后的代码命名清晰看了就知道是什么。函数短小每个只做一件事主函数apply_discount_to_order像一篇可读的散文。注释只存在于必要的地方函数文档字符串和少量解释且说明了“为什么”。结构清晰常量被集中管理数据用类封装依赖关系明确。 这样的代码无论是三个月后自己来改还是交给其他同事维护难度都大大降低。7. 常见问题与排查技巧实录即使遵循了好习惯在实际编码中还是会遇到各种问题。这里记录一些典型场景和我的处理思路。7.1 命名与作用域冲突问题在大型文件中变量名或函数名重复定义或者无意中覆盖了全局变量。排查使用IDE的“查找所有引用”功能检查同名符号的出现位置。在JavaScript等语言中严格使用let和const代替var利用块级作用域。对于模块善用export和import的具名导入/导出避免使用全局export default过多导致命名模糊。技巧给内部使用的辅助函数或变量加一个下划线前缀如_internalHelper这是一个广泛约定的“私有”标志虽然语言层面不一定强制但能有效提示作用域和意图。7.2 长函数如何拆分问题面对一个几百行的函数不知从何下手。方法寻找注释原函数中的注释块往往是天然的函数边界。每个注释描述的逻辑都可以尝试提取成一个函数。寻找代码块观察缩进相同的代码块特别是那些被空行隔开的段落。它们通常代表一个独立的步骤。识别动词代码中重复的操作如“计算”、“验证”、“保存”、“发送”往往是提取函数的好候选。自上而下先写一个高层次的主函数用一系列函数调用描述整个过程这些函数哪怕还不存在也没关系先写calculateDiscount,updateInventory这样的空函数。然后再去逐一实现这些子函数。这就是“抽象分层”。注意事项拆分时要注意函数间的数据传递。如果提取函数需要太多参数可能意味着这几个逻辑耦合过紧或者应该将它们封装进一个对象参数对象中传递。7.3 注释维护难题问题代码更新后注释容易忘记更新变成“过时注释”。应对策略将注释视为代码的一部分在代码评审Code Review时同时评审注释。修改代码后必须检查并更新相关注释。让注释靠近代码将注释写在它所解释的代码块正上方而不是很远的地方。这样修改代码时更容易看到注释。使用工具有些IDE插件或Lint工具可以检测到函数签名变更但文档注释未更新的情况。对于复杂的业务逻辑考虑将注释升级为单元测试。测试用例本身就是活的、可执行的文档。例如与其注释“VIP用户打9折”不如写一个测试用例test_vip_user_gets_10_percent_discount()。7.4 结构混乱依赖纠缠问题项目逐渐变大文件间import/require关系复杂循环依赖修改一处动全身。解决思路绘制依赖图用工具或手动画出模块间的依赖关系。寻找并打破循环依赖。通常引入一个双方都依赖的第三方抽象接口或基类可以解决。遵循依赖倒置原则高层模块不要直接导入低层模块的具体实现而是导入其抽象接口。让低层模块依赖高层定义的接口。这在TypeScript、Java等语言中很容易实现。使用依赖注入DI将依赖项通过构造函数或参数传入而不是在模块内部直接创建。这能极大提高代码的可测试性和灵活性。按功能垂直拆分如果一个大模块承载了太多不相关的功能果断将其拆分成多个小模块每个模块职责单一。7.5 IDE与工具相关技巧快捷键生成注释如“idea配置快速生产类注释的快捷键”。在IntelliJ IDEA中可以在Settings - Editor - Live Templates中自定义模板。例如定义一个cls模板内容为/** * $CLASS_NAME$ * $END$ */并将其关联到/**触发。这样输入/**再按Tab就能快速生成类注释框架。代码格式化统一使用Prettier、Black、gofmt等自动化代码格式化工具并集成到提交钩子pre-commit hook中。这能消除无意义的缩进、空格等风格争论让代码结构看起来一致。静态代码分析使用SonarQube、ESLint、Pylint等工具。它们不仅能发现语法错误还能检测出代码坏味道如过长的函数、过深的嵌套、未使用的变量等强制你保持代码干净。养成“干净代码”的习惯开头可能会觉得有点慢有点“麻烦”。但就像养成任何好习惯一样一旦形成肌肉记忆它会变成你的本能。你会发现你花在调试、理解旧代码、与同事沟通上的时间大大减少而花在创造新价值上的时间越来越多。代码不再是负担而是你清晰、有力的表达。从下一个变量命名、下一个短函数、下一行有意义的注释开始吧。
返回列表