Java 与 PHP 开发核心良好习惯笔记(含通用+语言特有)

良好的开发习惯不仅能提升代码可读性、可维护性,还能减少 Bug 产生、降低协作成本。结合通用开发规范与 Java/PHP 语言特性,在已有习惯基础上,补充核心习惯,形成完整的开发规范体系。
一、通用开发习惯(Java/PHP 均适用)
1. 代码格式规范统一,拒绝“随心所欲”
- 缩进与换行:统一使用 4 个空格缩进(禁止 Tab 缩进,避免不同编辑器显示异常);每个代码块(类、方法、循环、条件)前后空 1 行;运算符前后、逗号后加空格(如
int a = 1 + 2;List<String> list = Arrays.asList("a", "b");)。 - 命名规范:遵循“见名知意”原则,拒绝拼音、缩写(通用缩写如
UserDTO除外):- 类名:帕斯卡命名法(首字母大写,如
UserServiceOrderController); - 方法名/变量名:驼峰命名法(首字母小写,如
getUserInfo()orderId); - 常量:全大写+下划线分隔(如
MAX_RETRY_COUNTREDIS_KEY_PREFIX); - 数据库相关:表名小写+下划线(如
user_info),字段名与变量名对应(避免表字段u_id对应变量userId不一致)。
- 类名:帕斯卡命名法(首字母大写,如
- 文件与目录结构:按功能/模块划分目录,避免所有文件堆在同一目录:
- 例:Java 项目
com.company.module.controller/service/dao/model;PHP 项目app/controller/service/dao/model,确保“不同职责的代码在不同目录”。
- 例:Java 项目
- 开发思维:增量开发
- 在构思、开发功能时,代码要易拓展、易维护,保证后续增加新功能时,只做增量开发,而不是代码重构。
2. 异常处理:不吞异常、不裸抛异常
- 禁止“沉默异常”:避免
try-catch后不处理(如catch (Exception e) {}),至少打印异常日志(Java 用log.error("异常描述", e),PHP 用error_log("异常描述: " . $e->getMessage())),便于问题追溯。 - 精准捕获异常:不直接捕获
Exception父类,优先捕获具体异常(如 JavaSQLExceptionNullPointerException,PHPPDOExceptionInvalidArgumentException),减少异常范围。 - 自定义业务异常:复杂业务场景下,定义业务异常类(如
BusinessException),搭配错误码(如ERROR_USER_NOT_FOUND = 1001),统一异常返回格式(如{"code":1001, "msg":"用户不存在", "data":{}}),避免接口返回格式混乱。
3. 输入校验:前端校验是辅助,后端校验是底线
- 所有接口入参必须在后端做校验(前端校验可被绕过,不能依赖):
- Java:使用
javax.validation注解(如@NotNull@Min(1)@Pattern(regexp = "^1[3-9]\\d{9}$")),配合全局异常处理器返回校验结果; - PHP:使用框架校验器(如 ThinkPHP
validate()方法、Laravel$request->validate())或自定义校验逻辑,避免直接使用$_GET/$_POST数据。
- Java:使用
- 校验场景:必填项、数据类型(如
id必须是正整数)、格式(手机号、邮箱、日期)、业务规则(如“下单时库存需大于 0”)。
4. 复用代码:拒绝复制粘贴,提取公共逻辑(兼顾实用性)
- 重复出现 2 次以上的代码,优先提取为公共方法/工具类:
- 例:日期格式化、字符串处理、加密解密、HTTP 请求等通用逻辑,封装为
DateUtilStringUtilHttpUtil; - 业务层面重复逻辑(如“用户权限校验”“订单状态判断”),提取为服务层公共方法(如
UserService::checkPermission())。
- 例:日期格式化、字符串处理、加密解密、HTTP 请求等通用逻辑,封装为
- 提取公共方法的“边界原则”:若提取后代码量大于原本代码量、或需要额外传递多个参数才能复用(导致逻辑更复杂),则无需提取——复用的核心是“简化代码、提升可维护性”,而非“为了提取而提取”。
- 避免“超大方法”:单个方法代码行数控制在 50 行以内,超过则拆分(如一个“下单接口”拆分为
checkStock()createOrder()notifyPay()等子方法),提升可读性。
5. 版本控制:规范 Git 操作,便于协作与回滚
- 分支管理:遵循“主分支+开发分支”规范:
master/main:生产环境分支,仅存放稳定代码,禁止直接提交;dev:开发分支,所有功能开发在dev或基于dev创建的特性分支(如feature/user-login);- 修复生产 Bug 用
hotfix分支(如hotfix/order-payment-error),修复后合并到master和dev。
- 提交规范:提交信息按“类型: 描述”格式编写(如
feat: 新增用户登录接口fix: 修复订单列表分页异常refactor: 优化用户服务代码结构),避免“修改代码”“fix bug”等模糊描述。 - 提交前检查:提交前执行代码格式化、本地测试,避免提交语法错误、未完成代码;多人协作时,提交前先
git pull拉取最新代码,解决冲突后再提交。
6. 数据库操作:安全、高效、可维护
- 避免 SQL 注入:禁止拼接 SQL 字符串(如
String sql = "select * from user where username = '" + username + "'"),必须使用参数绑定:- Java:用
PreparedStatement或 MyBatis 占位符(#{username}); - PHP:用 PDO 预处理(
$stmt->execute([$username]))或框架 ORM 自带参数绑定。
- Java:用
- 优化 SQL 性能:
- 复杂查询先在 DBeaver/Navicat 中测试执行计划(
EXPLAIN),确保走索引(避免full table scan); - 分页查询必加
limit,禁止查询全表(如select * from order无限制); - 批量操作使用批量插入/更新(如 Java
MyBatis BatchExecutor,PHPPDO::exec()批量 SQL),避免循环单条操作数据库。
- 复杂查询先在 DBeaver/Navicat 中测试执行计划(
- 事务管理:涉及多表操作(如“下单减库存”),必须加事务,确保原子性:
- Java:用
@Transactional注解(指定异常回滚条件); - PHP:用
try { $pdo->beginTransaction(); ... $pdo->commit(); } catch (Exception $e) { $pdo->rollBack(); }。
- Java:用
7. 注释规范:注释是“代码说明书”,不是冗余描述
- 必须加注释的场景:
- 类/接口:说明核心职责、作者、创建日期(如
/** 订单服务,处理订单创建、支付、取消等业务 @author 用户名 @date 2025-01-01 */); - 方法:说明入参含义、返回值格式、异常情况(如
/** 获取用户信息 @param userId 用户ID @return UserDTO 用户信息DTO @throws BusinessException 1001-用户不存在 */); - 复杂逻辑:核心业务逻辑(如“折扣计算规则”“状态流转逻辑”)加行注释,说明设计思路(避免后续维护者看不懂“为什么这么写”)。
- 类/接口:说明核心职责、作者、创建日期(如
- 禁止冗余注释:避免注释与代码重复(如
// 定义用户ID int userId;),注释应补充代码未表达的信息(如// 兼容旧系统,用户ID允许为字符串格式)。
二、Java 开发特有良好习惯
1. 面向对象:遵循设计原则,拒绝“面条代码”
- 单一职责:一个类/方法只做一件事(如
UserController只处理用户相关接口,不包含订单逻辑); - 依赖注入(DI):使用 Spring 依赖注入(
@Autowired@Resource),避免手动new对象(如UserService userService = new UserService();),降低代码耦合; - 接口编程:复杂业务场景下,优先定义接口(如
UserService),再实现类(UserServiceImpl),便于后续扩展(如新增UserServiceV2Impl兼容新需求); - 避免过度封装:封装是为了简化使用,不是“越复杂越好”(如一个简单的字符串判断,无需封装为多层类)。
2. 异常处理强化:抛出必处理,不裸抛无追溯
- 抛出异常必须有处理链路:方法声明
throws Exception(或具体异常)后,调用该方法的所有上游链路(如 Controller → Service → Dao)中,必须有一处进行try-catch处理(打印日志、返回错误响应等),禁止“裸抛异常”导致程序崩溃。- 示例:
Service层方法public UserDTO getUserById(Long userId) throws BusinessException,Controller层调用时必须捕获:@GetMapping("/user/{id}") public Result<UserDTO> getUser(@PathVariable Long id) {try {UserDTO user = userService.getUserById(id);return Result.success(user);} catch (BusinessException e) {log.error("获取用户失败,用户ID:{}", id, e);return Result.error(e.getCode(), e.getMessage());} }
- 示例:
- 禁止方法抛出
Exception父类:方法声明抛出异常时,优先抛出具体业务异常(如BusinessException)或框架特定异常(如SQLException),让调用方清晰知道需要处理的异常类型,避免“一刀切”捕获所有异常。
3. 空值处理:拒绝返回 null,强制判空防 NPE
- 方法返回值避免 null:接口或工具方法返回集合、对象时,优先返回默认值而非
null:- 集合:返回空集合(
Collections.emptyList()new ArrayList<>()),而非null; - 对象:简单场景返回空对象(如
new EmptyUserDTO()),复杂场景用Optional包装(Java 8+),明确告知调用方“可能无数据”; - 基本类型:返回默认值(如
int返回0,boolean返回false)。
- 集合:返回空集合(
- 强制判空校验:所有入参、方法返回值、对象属性使用前,必须做判空处理:
- 简单判空:
if (userId == null) { throw new IllegalArgumentException("用户ID不能为空"); }; - 链式判空:用
Optional简化(如Optional.ofNullable(user).map(User::getAddress).map(Address::getCity).orElse("默认城市")),避免多层if (obj != null)嵌套; - 集合判空:
if (CollectionUtils.isEmpty(list)) { return Result.success(Collections.emptyList()); }(使用org.springframework.util.CollectionUtils工具类)。
- 简单判空:
4. 内存与资源:避免泄露,及时释放
- 关闭资源:数据库连接、IO 流、Redis 连接等资源,必须在
finally中关闭或使用 try-with-resources(Java 7+):// 推荐 try-with-resources(自动关闭资源) try (InputStream is = new FileInputStream("file.txt");OutputStream os = new FileOutputStream("output.txt")) {// 操作流 } catch (IOException e) {log.error("IO 异常", e); } - 慎用静态变量:静态变量生命周期与应用一致,避免存储大量数据(如
static List<User> allUsers),容易导致内存溢出; - 集合操作:
- 初始化集合时指定容量(如
List<String> list = new ArrayList<>(10);),避免频繁扩容; - 遍历集合时禁止修改(如
for (User user : list) { list.remove(user); }),用迭代器(Iterator)或stream操作; - 避免
null集合返回,用空集合替代(如return Collections.emptyList();),减少NullPointerException。
- 初始化集合时指定容量(如
5. 框架使用:遵循框架规范,不“反骨”
- Spring Boot 规范:
- 控制器(
@Controller@RestController)只负责接收请求、返回响应,不包含业务逻辑(业务逻辑放在@Service层); - 数据访问(
@Repository)只负责数据库操作,不包含业务规则判断; - 配置文件(
application.yml)中区分环境(dev/test/prod),敏感信息(数据库密码、密钥)用配置中心(如 Nacos)或环境变量,禁止硬编码。
- 控制器(
- MyBatis 规范:
- Mapper 接口与 XML 文件同名同目录,SQL 语句写在 XML 中(复杂 SQL)或用注解(简单 SQL,如
@Select("select * from user where id = #{id}")); - 避免在 XML 中写复杂逻辑(如多层
if-else),复杂逻辑放在服务层处理; - 分页查询使用
PageHelper等工具,统一分页参数(如pageNumpageSize)。 MyBatis-Plus 核心规范
实体类与表映射
使用@TableName注解明确实体类与数据库表名映射,字段与列名通过@TableField注解配置。若遵循驼峰转下划线命名约定,可省略部分注解。Mapper 接口规范
继承BaseMapper<T>获得基础 CRUD 方法,无需编写 XML。自定义方法需通过注解(如@Select)或 XML 实现,XML 文件建议放在resources/mapper目录下。Service 层规范
Service 接口继承IService<T>,实现类继承ServiceImpl<M, T>并实现接口。复杂业务逻辑在 Service 层处理,避免在 Mapper 层编写。1.SQL 编写与注解
简单查询
优先使用 MyBatis-Plus 内置方法(如selectById)。条件构造器QueryWrapper或LambdaQueryWrapper构建动态查询:LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getName, "张三").gt(User::getAge, 18);2.复杂 SQL
XML 中编写多表关联或复杂聚合查询,但需避免 Java 逻辑(如循环判断)。注解方式适用于简单 SQL:@Select("SELECT * FROM user WHERE status = #{status}") List<User> selectByStatus(@Param("status") Integer status);3.分页与性能优化
分页实现
配置分页插件后,直接使用Page对象:Page<User> page = new Page<>(pageNum, pageSize); userMapper.selectPage(page, wrapper);4.性能建议
- 避免
select *,明确查询字段 - 大数据量查询使用流式处理或分页
- 索引字段需在 Wrapper 中优先作为条件
- Mapper 接口与 XML 文件同名同目录,SQL 语句写在 XML 中(复杂 SQL)或用注解(简单 SQL,如
6. 调试与测试:提升问题定位效率
- 单元测试:核心服务层方法编写 JUnit 测试(如
UserServiceTest),覆盖正常场景、异常场景(如“用户不存在”“参数为空”),确保修改代码后不影响原有功能; - 日志分级:按场景使用不同日志级别(
debug调试信息、info业务日志、warn警告信息、error错误信息),生产环境关闭debug日志,避免日志泛滥; - 远程调试:生产环境无法本地调试时,开启远程调试(JVM 参数
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005),配合 IDEA 远程连接定位问题(注意生产环境开启后及时关闭,避免安全风险)。
三、PHP 开发特有良好习惯
1. 环境与配置:统一环境,避免“本地能跑,线上报错”
- PHP 版本统一:团队成员使用相同 PHP 版本(如 7.4+),避免因版本差异导致语法不兼容(如 PHP 7+ 新增的类型声明、箭头函数);
- 配置文件分离:区分开发/测试/生产配置(如
config/dev.phpconfig/prod.php),通过环境变量($_ENV['APP_ENV'])切换,敏感信息(数据库密码)放在环境变量或.env文件(禁止提交到 Git); - 开启错误提示:开发环境开启
error_reporting(E_ALL)display_errors=On,及时发现语法错误;生产环境关闭display_errors=Off,开启log_errors=On,错误日志写入文件(避免暴露敏感信息给用户)。
2. 语法与性能:避免“低效语法”“危险语法”
- 避免全局变量/函数:全局变量(
$GLOBALS)、全局函数容易导致变量污染,优先用类/命名空间封装; - 数组操作:
- 用
isset($arr['key'])或array_key_exists('key', $arr)判断数组键是否存在,避免$arr['key']不存在时报错; - 遍历数组优先用
foreach(如foreach ($list as $item) {}),避免for循环(需获取数组长度,效率较低); - 数组初始化用短语法(
$arr = ['a', 'b']),替代array('a', 'b')。
- 用
- 字符串拼接:短字符串用
.拼接(如$name = $user['name'] . '先生'),长字符串用 heredoc(<<<EOD ... EOD;),避免频繁拼接导致性能损耗; - 避免
eval()函数:eval()执行字符串代码,存在安全风险(如注入恶意代码),尽量用其他方式替代。
3. 框架使用:遵循框架约定,提升开发效率
- ThinkPHP/Laravel 规范:
- 控制器(
app/controller)方法返回统一格式(如return json(['code'=>200, 'msg'=>'success', 'data'=>$data])); - 模型(
app/model)定义表名、字段映射(如 ThinkPHPprotected $table = 'user_info'; protected $schema = ['id'=>'int', 'name'=>'string'];),避免硬编码表名; - 中间件(
app/middleware)处理通用逻辑(如登录校验、日志记录),避免在控制器中重复编写。
- 控制器(
- ORM 操作:优先使用框架 ORM 封装的方法(如
User::where('id', $id)->find()),复杂 SQL 用查询构造器(如DB::table('user')->where('age', '>', 18)->select()),避免直接拼接 SQL。
4. 调试与日志:快速定位问题
- 日志查看:除了
runtime/logs中的 SQL 日志,还需关注错误日志(runtime/logs/error-xxx.log),日志中记录请求参数、响应结果、异常堆栈(便于复现问题); - 调试工具:开发环境使用
dump()dd()(Laravel)或var_dump()打印变量,但提交前必须删除(避免泄露敏感信息); - XDebug 调试:配置 XDebug 配合 PhpStorm,支持断点调试、变量查看,替代“打印变量”的低效调试方式。
四、总结:习惯的核心是“降低成本”
良好的开发习惯本质是“降低个人维护成本、团队协作成本、问题定位成本”:
- 代码格式、命名、注释规范:让自己和他人“一眼看懂代码”;
- 异常处理、输入校验、事务管理:减少 Bug 产生,降低线上故障风险;
- 复用代码、版本控制、框架规范:提升开发效率,便于协作与迭代;
- Java 特有的“异常链路处理”“空值防护”“公共方法提取边界”:针对性解决 Java 语言高频问题(NPE、异常崩溃、代码冗余)。
以上习惯需在日常开发中刻意练习(如提交代码前自查格式、写完接口加校验、复杂逻辑写注释、返回值避免 null),形成肌肉记忆后,既能提升代码质量,也能让自己成为“更靠谱的开发者”。
