跨平台编码规范文档
1. 引言
1.1 目的与范围
本编码规范旨在为软件开发团队提供统一的代码编写标准,以提高代码质量、可维护性和团队协作效率。适用于使用C#、Java、安卓和Web前端(HTML/CSS/JavaScript/TypeScript)的项目开发,不针对特定语言特性,而是聚焦于通用的编程原则和最佳实践。
1.2 适用场景
- 团队协作开发:确保代码风格一致性,降低理解成本。
- 代码审查:作为评审依据,提升代码质量。
- 项目交接:规范的代码结构便于后续维护和扩展。
- 新人培训:帮助新成员快速融入团队开发流程。
1.3 参考标准
本规范参考了以下行业标准和最佳实践:
- 通用原则:SOLID设计原则、DRY(Don’t Repeat Yourself)原则、KISS(Keep It Simple, Stupid)原则。
- 语言特定规范:
- C#:Microsoft .NET编码规范
- Java:Google Java Style Guide
- 安卓:Android Code Style Guidelines
- Web前端:Airbnb JavaScript Style Guide、Google HTML/CSS Style Guide
- 安全标准:OWASP Top Ten、CWE/SANS Top 25。
1.4 规范优先级
- 强制性规则:必须严格遵守,违反可能导致安全风险、功能故障或严重影响代码可维护性。
- 推荐实践:建议遵循,有助于提升代码质量和可读性。
- 可选建议:根据项目需求和团队偏好选择实施。
1.5 版本控制
本规范将定期更新以适应技术发展和团队实践反馈。版本历史记录见附录X。团队成员应定期关注规范更新,并在新项目或迭代中应用最新标准。
2. 代码格式
2.1 缩进与空格
-
统一缩进方式
- 推荐使用4个空格进行缩进(避免使用Tab,防止不同编辑器显示差异)。
- 例外:HTML/XML标签嵌套可使用2个空格以提高可读性。
-
括号与空格
- 函数/方法调用:括号前不加空格,如
functionName(param1, param2)
。 - 条件语句:关键字与括号间留空格,如
if (condition) { ... }
。 - 对象/数组字面量:键值对、元素间用逗号后加空格,如
const obj = { key: value, anotherKey: 123 };
。
- 函数/方法调用:括号前不加空格,如
-
运算符空格
- 二元运算符(如
+
,-
,=
,==
,&&
)两侧各留一个空格,如x = a + b * c;
。 - 一元运算符(如
!
,++
,--
)后不加空格,如let y = !isValid;
。
- 二元运算符(如
2.2 行长度限制
- 单行最大长度:建议不超过120个字符。
- 长表达式换行:
- 在运算符后换行,保持逻辑清晰。
- 后续行缩进2级(8个空格),如:
// Java示例 String longString = "This is a very long string that exceeds the line length limit " + "so we break it into multiple lines for readability.";
2.3 注释规范
-
单行注释(
//
):用于解释单行逻辑或临时注释代码。// 计算平均值 const avg = sum / count;
-
多行注释(
/* ... */
):避免使用,改用连续单行注释。// 不推荐 /* 这是一段多行注释 描述函数的功能 */ // 推荐 // 这是一段多行注释 // 描述函数的功能
-
文档注释:
- C#/Java:使用
///
(C#)或/** ... */
(Java)生成API文档。/// <summary> /// 计算两数之和 /// </summary> /// <param name="a">第一个数</param> /// <param name="b">第二个数</param> /// <returns>两数之和</returns> public int Add(int a, int b) { ... }
- Web前端:使用JSDoc/TSDoc规范。
/** * 计算两数之和 * @param {number} a - 第一个数 * @param {number} b - 第二个数 * @returns {number} 两数之和 */ function add(a, b) { ... }
- C#/Java:使用
-
注释原则:
- 解释为什么而非是什么,如:
// 循环提前终止以避免性能问题(数据量超过1000时会导致OOM) for (int i = 0; i < Math.min(items.size(), 1000); i++) { ... }
- 避免冗余注释:
// 不推荐:注释重复代码逻辑 x = x + 1; // 增加x的值 // 推荐:解释业务含义 x = x + 1; // 更新计数器,触发下一页加载
- 解释为什么而非是什么,如:
2.4 空行与分段
-
逻辑分段:使用空行分隔不同功能块,如:
public void processOrder(Order order) { // 验证订单 if (order == null) { throw new IllegalArgumentException("订单不能为空"); } // 计算总价 double total = calculateTotal(order); // 保存订单 saveOrder(order, total); }
-
文件顶部留白:类/文件开头保留1个空行,如:
import os import sys class MyClass: ...
-
连续空行限制:不超过2个连续空行。
2.5 括号风格
-
K&R风格(推荐):左大括号与语句同行,右大括号独占一行。
if (condition) { // 代码块 } else { // 代码块 }
-
例外情况:
- 单行方法/函数:可省略括号(需团队统一)。
public int getCount() { return count; }
- 单行方法/函数:可省略括号(需团队统一)。
2.6 对齐规则
- 变量声明:无需强制对齐赋值符号,保持代码简洁。
// 不推荐(过度对齐增加维护成本) int id = 1; string name = "John"; DateTime birthday = DateTime.Now; // 推荐 int id = 1; string name = "John"; DateTime birthday = DateTime.Now;
2.7 特殊字符使用
- 行尾分号:必须使用(即使语言允许省略,如JavaScript)。
- 字符串引号:统一使用单引号(
'
)或双引号("
),推荐:- Web前端:单引号用于HTML属性,双引号用于JavaScript字符串。
- C#/Java:双引号用于字符串,单引号用于字符。
示例对比
不规范代码:
function calculateTotal(items){let total=0;
for(let i=0;i<items.length;i++){
total+=items[i].price*items[i].quantity;}
return total;}
规范代码:
/** * 计算商品总价 * @param {Array<Object>} items - 商品列表 * @returns {number} 总价 */
function calculateTotal(items) { let total = 0; for (let i = 0; i < items.length; i++) { total += items[i].price * items[i].quantity; } return total;
}
3. 命名规范
3.1 标识符命名原则
- 一致性原则:项目内保持统一的命名风格(如驼峰式、帕斯卡式、蛇形式)。
- 可读性优先:使用完整单词或通用缩写,避免无意义字符(如
a
,temp
)。 - 避免冗余:类名/接口名不包含类型后缀(如
UserModel
,ProductService
)。
3.2 命名风格约定
元素类型 | 命名风格 | 示例 |
---|---|---|
变量 | 小驼峰 (camelCase) | userId , totalCount |
常量 | 全大写 + 下划线 | MAX_LENGTH , API_KEY |
函数/方法 | 小驼峰 (camelCase) | getUserById() , calculateTotal() |
类/接口 | 大驼峰 (PascalCase) | User , IEnumerable |
命名空间/包 | 小写 + 点分隔 | com.example.util , System.IO |
文件/目录 | 小写 + 连字符 | user-profile.js , components/layout |
3.3 变量命名规范
- 类型暗示:避免使用类型前缀(如
strName
,intCount
)。 - 集合命名:使用复数或集合术语(如
users
,productList
)。 - 布尔变量:使用
is/has/can
前缀(如isValid
,hasPermission
)。
示例:
// 不推荐
String sName = "John";
ArrayList<Integer> list = new ArrayList<>();
boolean valid = true;// 推荐
String name = "John";
List<Integer> scores = new ArrayList<>();
boolean isValid = true;
3.4 函数/方法命名规范
- 动宾结构:明确表达功能(如
saveUser()
,validateInput()
)。 - 查询方法:使用
get/is/has
前缀(如getUser()
,isEmpty()
)。 - 修改方法:使用
set/update/add/remove
前缀(如setName()
,removeItem()
)。
示例:
// 不推荐
function process(x) { /* ... */ }
function data() { return users; }// 推荐
function calculateTax(amount) { /* ... */ }
function getUsers() { return users; }
3.5 类/接口命名规范
- 名词/形容词:避免动词(如
User
,Configuration
,ReadOnlyCollection
)。 - 接口命名:推荐使用
I
前缀(如IEnumerable
,IValidator
)。
示例:
// 不推荐
class ProcessOrder { /* ... */ }
interface Validatable { /* ... */ }// 推荐
class OrderProcessor { /* ... */ }
interface IValidator { bool Validate(); }
3.6 命名空间/模块命名规范
- 领域分层:按业务领域或功能模块划分(如
com.example.auth
,app/services
)。 - 避免泛型名称:不使用
utils
,helpers
等模糊命名。
3.7 缩写与简写规范
- 使用公认缩写:如
id
(标识符)、num
(数字)、avg
(平均值)。 - 避免自创缩写:如
nbr
(number)、msg
(message)应使用完整形式。
3.8 测试相关命名
- 测试类:使用
[被测试类]Test
命名(如UserServiceTest
)。 - 测试方法:使用
[测试场景]_[预期行为]
模式(如whenInputIsEmpty_throwException()
)。
示例:
public class CalculatorTest {@Testpublic void givenTwoNumbers_whenAdded_returnsSum() {// 测试逻辑}
}
3.9 避免的命名陷阱
- 保留字冲突:不使用语言保留字(如
class
,function
,null
)。 - 视觉相似字符:避免使用
l
(小写L)、I
(大写i)、O
(大写o)作为单字符变量。 - 文化敏感词汇:避免使用可能引起歧义的词汇(如
master/slave
可用primary/replica
替代)。
命名检查清单
- 能否通过名称清晰理解功能?
- 是否符合团队统一的命名风格?
- 是否包含不必要的类型信息?
- 是否使用了有歧义的缩写或简写?
- 是否与现有命名模式一致?
4. 代码结构
4.1 文件组织与目录结构
-
按领域/功能划分:
- 避免按技术类型分类(如
controllers/
,services/
),推荐按业务领域组织(如user/
,order/
,payment/
)。 - 示例:
src/ ├── user/ │ ├── UserModel.cs │ ├── UserController.js │ ├── UserService.java │ └── tests/ ├── order/ │ ├── OrderEntity.ts │ ├── OrderRepository.kt │ └── OrderApi.vue └── shared/ ├── utils/ ├── constants/ └── exceptions/
- 避免按技术类型分类(如
-
避免深层嵌套:目录层级不超过4层,超过时应重构为模块。
4.2 模块化设计原则
-
单一职责:每个模块/类/函数仅负责一个明确的功能。
-
高内聚低耦合:
- 内聚性:模块内部元素关联紧密(如
UserService
专注用户业务逻辑)。 - 耦合度:模块间依赖明确且可控(通过接口而非具体实现交互)。
- 内聚性:模块内部元素关联紧密(如
-
依赖倒置原则:
- 高层模块不依赖低层模块,二者依赖抽象接口。
- 示例:
// 不推荐:高层依赖具体实现 class OrderService { private MySQLUserRepository userRepo = new MySQLUserRepository(); } // 推荐:依赖抽象接口 class OrderService { private IUserRepository userRepo; public OrderService(IUserRepository repo) { this.userRepo = repo; } }
4.3 组件/类设计规范
- 类大小限制:单个类代码不超过500行(特殊情况需评审)。
- 构造函数简洁性:参数不超过5个,复杂初始化使用Builder模式。
- 方法长度限制:单个方法不超过30行,避免过长逻辑链。
4.4 分层架构
-
典型分层(适用于后端/前端):
- 表示层(UI/API):处理用户交互和HTTP请求。
- 应用层:协调业务流程,不包含核心业务逻辑。
- 领域层:核心业务逻辑、实体和值对象。
- 基础设施层:数据访问、外部服务调用等。
示例(Java后端):
// 表示层 @RestController public class UserController { private final UserService userService; public UserController(UserService service) { this.userService = service; } @PostMapping("/users") public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO dto) { return ResponseEntity.ok(userService.createUser(dto)); } } // 应用层 @Service public class UserService { private final UserDomainService domainService; public UserService(UserDomainService service) { this.domainService = service; } public UserDTO createUser(UserDTO dto) { User user = domainService.registerUser(dto.getName(), dto.getEmail()); return mapToDTO(user); } } // 领域层 @DomainService public class UserDomainService { public User registerUser(String name, String email) { // 核心业务逻辑:验证邮箱、生成ID、创建用户 if (!EmailValidator.isValid(email)) { throw new InvalidEmailException(email); } return new User(UUID.randomUUID(), name, email); } }
4.5 前端组件结构
-
组件职责分离:
- 容器组件:负责数据获取和状态管理。
- 展示组件:专注UI渲染,无业务逻辑。
示例(React):
// 容器组件(UserListContainer.jsx) const UserListContainer = () => { const [users, setUsers] = useState([]); useEffect(() => { fetchUsers().then(data => setUsers(data)); }, []); return <UserList users={users} onDelete={deleteUser} />; }; // 展示组件(UserList.jsx) const UserList = ({ users, onDelete }) => ( <div> {users.map(user => ( <UserItem key={user.id} user={user} onDelete={onDelete} /> ))} </div> );
4.6 依赖管理规范
-
显式依赖声明:
- 使用包管理工具(npm, Maven, NuGet等)明确声明依赖版本。
- 避免全局依赖(如全局安装的工具库)。
-
版本控制:
- 使用语义化版本(SemVer),锁定次要版本(如
^1.2.3
)。 - 定期更新依赖(至少每季度一次),使用工具(如Dependabot)自动检测安全漏洞。
- 使用语义化版本(SemVer),锁定次要版本(如
4.7 配置管理
-
环境分离:配置文件按环境区分(如
config/dev.properties
,config/prod.json
)。 -
敏感信息:不硬编码密钥、数据库连接串等,使用环境变量或加密存储。
示例(Node.js):
// 不推荐 const dbConfig = { host: "localhost", password: "mysecretpassword" }; // 推荐 const dbConfig = { host: process.env.DB_HOST || "localhost", password: process.env.DB_PASSWORD };
4.8 代码复用策略
-
避免复制粘贴:提取公共逻辑为工具类、组件或库。
-
组合优于继承:优先使用接口组合而非类继承,减少耦合。
示例(Python):
# 不推荐:继承导致紧耦合 class Bird: def fly(self): pass class Penguin(Bird): def fly(self): raise NotImplementedError() # 推荐:接口组合 class Flyable: def fly(self): pass class Bird: def __init__(self, movement: Flyable = None): self.movement = movement class Penguin(Bird): def __init__(self): super().__init__(movement=None)
代码结构检查清单
- 是否遵循单一职责原则?
- 依赖关系是否清晰且符合依赖倒置原则?
- 目录结构是否反映业务领域而非技术实现?
- 组件/类大小是否可控?
- 配置与敏感信息是否妥善管理?
5. 控制结构
5.1 条件语句规范
-
简化布尔表达式:避免冗余的
== true
或== false
。// 不推荐 if (isValid == true) { ... } if (hasPermission == false) { ... } // 推荐 if (isValid) { ... } if (!hasPermission) { ... }
-
卫语句优先:提前处理异常情况,减少嵌套。
// 不推荐 public void processOrder(Order order) { if (order != null) { if (order.isPaid()) { if (order.getItems().size() > 0) { // 主逻辑 } else { throw new InvalidOrderException("订单不能为空"); } } else { throw new PaymentRequiredException(); } } else { throw new IllegalArgumentException("订单不能为空"); } } // 推荐 public void processOrder(Order order) { if (order == null) throw new IllegalArgumentException("订单不能为空"); if (!order.isPaid()) throw new PaymentRequiredException(); if (order.getItems().isEmpty()) throw new InvalidOrderException("订单不能为空"); // 主逻辑(无嵌套) }
-
三元运算符限制:仅用于简单条件赋值,避免复杂逻辑。
// 推荐 string status = isActive ? "Active" : "Inactive"; // 不推荐 var result = x > 10 ? (y < 5 ? "A" : "B") : (z != 0 ? "C" : "D");
5.2 循环语句规范
-
优先使用高级抽象:使用
forEach
,map
,filter
等替代原始for
循环(支持时)。// 不推荐 const users = []; for (let i = 0; i < data.length; i++) { if (data[i].isActive) { users.push(data[i]); } } // 推荐 const users = data.filter(item => item.isActive);
-
避免无限循环:确保循环条件可终止,使用超时机制(如网络请求)。
# 危险示例 while True: result = fetch_data() # 若请求失败可能导致死循环 process(result) # 推荐 max_attempts = 3 for attempt in range(max_attempts): try: result = fetch_data() process(result) break except Exception as e: if attempt == max_attempts - 1: raise time.sleep(1) # 重试间隔
-
循环变量作用域:避免循环变量泄露(如JavaScript中的
var
)。// 不推荐(var作用域问题) for (var i = 0; i < 10; i++) { setTimeout(() => console.log(i), 100); // 全部输出10 } // 推荐(let块级作用域) for (let i = 0; i < 10; i++) { setTimeout(() => console.log(i), 100); // 输出0-9 }
5.3 Switch/Case 规范
-
默认分支:必须包含
default
分支(即使为空)。 -
贯穿行为:明确标注
fallthrough
(如Java中使用注释)。switch (status) { case ACTIVE: processActive(); // fallthrough case PENDING: processPending(); break; default: // 处理未知状态 throw new IllegalArgumentException("未知状态: " + status); }
-
避免复杂逻辑:每个
case
逻辑不超过10行,复杂逻辑提取为方法。
5.4 异常处理规范
-
具体异常捕获:避免捕获通用异常(如
Exception
),优先处理特定异常。// 不推荐 try { file.Open(); } catch (Exception ex) { Logger.Log(ex.Message); // 可能掩盖真正问题 } // 推荐 try { file.Open(); } catch (FileNotFoundException ex) { Logger.Log("文件不存在: " + ex.Path); } catch (UnauthorizedAccessException ex) { Logger.Log("无访问权限: " + ex.Message); }
-
异常传播:底层组件抛出异常,高层组件处理。
def read_config(file_path): if not os.path.exists(file_path): raise FileNotFoundError(f"配置文件不存在: {file_path}") # 抛出异常 # ... def main(): try: config = read_config("app.conf") except FileNotFoundError as e: print(f"启动失败: {e}") # 高层处理 sys.exit(1)
-
资源管理:使用
using
(C#)、try-with-resources
(Java)或with
(Python)确保资源释放。// Java示例 try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) { return reader.lines().collect(Collectors.joining()); } // 自动关闭reader
5.5 空值处理规范
-
防御性编程:检查参数和返回值是否为
null
/None
。// 不推荐 function calculateTotal(orders: Order[]): number { return orders.reduce((sum, order) => sum + order.amount, 0); // 若orders为null会报错 } // 推荐 function calculateTotal(orders: Order[] | null): number { if (!orders) return 0; return orders.reduce((sum, order) => sum + order.amount, 0); }
-
空对象模式:使用空对象替代
null
返回值(如Optional.empty()
in Java)。// 推荐 public Optional<User> getUserById(String id) { User user = userRepository.findById(id); return user != null ? Optional.of(user) : Optional.empty(); } // 使用 userService.getUserById("123") .ifPresent(user -> process(user));
5.6 控制结构复杂度限制
- 圈复杂度(Cyclomatic Complexity):单个方法圈复杂度不超过10。
- 使用工具(如SonarQube, ESLint)检测并重构复杂逻辑。
- 嵌套层级:条件/循环嵌套不超过3层,超过时应提取方法。
控制结构最佳实践总结
- 保持代码扁平:减少嵌套,优先使用卫语句。
- 抽象替代复杂条件:将复杂条件逻辑提取为布尔方法。
- 异常处理策略:
- 抛出明确异常,避免捕获通用异常。
- 资源管理使用语言提供的自动释放机制。
- 空值安全:
- 使用语言特性(如
Optional
,??
操作符)处理空值。 - 避免
null
作为方法返回值(优先返回空集合或空对象)。
- 使用语言特性(如
6. 面向对象原则
6.1 类设计原则
-
单一职责原则(SRP):每个类只负责一个明确的功能。
// 不推荐:UserManager承担多个职责 class UserManager { public void createUser(String name, String email) { /* 创建用户 */ } public void sendWelcomeEmail(String email) { /* 发送邮件 */ } public void generateReport() { /* 生成报表 */ } } // 推荐:职责分离 class UserService { public void createUser(String name, String email) { /* 创建用户 */ } } class EmailService { public void sendWelcomeEmail(String email) { /* 发送邮件 */ } } class ReportGenerator { public void generateReport() { /* 生成报表 */ } }
-
开闭原则(OCP):对扩展开放,对修改关闭。
- 使用接口/抽象类定义行为,通过实现类扩展功能。
// 基类 public abstract class DiscountStrategy { public abstract decimal CalculateDiscount(Order order); } // 具体实现 public class ChristmasDiscount : DiscountStrategy { public override decimal CalculateDiscount(Order order) { return order.Total * 0.2m; } } // 使用策略 public class OrderProcessor { private readonly DiscountStrategy _strategy; public OrderProcessor(DiscountStrategy strategy) { _strategy = strategy; } }
6.2 继承与组合
-
组合优先:通过组合实现代码复用,减少继承层级。
# 不推荐:多层继承导致耦合 class Animal: def move(self): pass class Bird(Animal): def fly(self): pass class Penguin(Bird): def fly(self): raise NotImplementedError() # 推荐:组合替代继承 class FlyBehavior: def fly(self): pass class Animal: def __init__(self, movement): self.movement = movement class Penguin(Animal): def __init__(self): super().__init__(movement=None) # 企鹅不会飞
-
继承规范:
- 子类必须完全理解父类行为(里氏替换原则)。
- 避免修改父类非抽象方法的行为(除非明确允许)。
6.3 接口设计规范
-
单一职责:接口只定义一组相关行为。
// 不推荐:大而全的接口 interface UserService { void createUser(String name); void validateEmail(String email); void sendNotification(String message); } // 推荐:拆分为多个小接口 interface UserManagement { void createUser(String name); } interface EmailValidator { boolean validateEmail(String email); } interface NotificationSender { void sendNotification(String message); }
-
最小接口原则:不强迫实现类依赖不需要的方法(ISP原则)。
6.4 抽象类 vs 接口
- 抽象类:定义部分实现,用于代码复用(如模板方法模式)。
- 接口:定义纯行为契约,支持多重实现。
6.5 设计模式应用
- 常用模式:
-
工厂模式:创建对象时隐藏具体实现逻辑。
class ProductFactory { static createProduct(type) { if (type === "A") return new ProductA(); if (type === "B") return new ProductB(); throw new Error("未知产品类型"); } }
-
单例模式:确保类只有一个实例。
public sealed class AppConfig { private static readonly Lazy<AppConfig> _instance = new Lazy<AppConfig>(() => new AppConfig()); public static AppConfig Instance => _instance.Value; private AppConfig() { } // 私有构造函数 }
-
观察者模式:对象间一对多依赖,当一个对象状态变化时通知所有依赖者。
interface Observer { void update(String message); } class Subject { private List<Observer> observers = new ArrayList<>(); public void addObserver(Observer observer) { observers.add(observer); } public void notifyObservers(String message) { observers.forEach(o => o.update(message)); } }
-
6.6 数据封装
- 访问控制:
- 字段设为私有(
private
),通过公共方法(getter/setter
)访问。 - 避免暴露内部数据结构(如返回集合的直接引用)。
// 不推荐 public class User { public List<String> roles; // 直接暴露字段 } // 推荐 public class User { private List<String> roles = new ArrayList<>(); public List<String> getRoles() { return Collections.unmodifiableList(roles); // 返回不可变视图 } public void addRole(String role) { this.roles.add(role); } }
- 字段设为私有(
6.7 依赖注入(DI)
-
构造函数注入:优先通过构造函数提供依赖。
class OrderService { constructor(private paymentGateway: PaymentGateway) {} public processOrder(order: Order) { this.paymentGateway.charge(order.total); } }
-
避免静态依赖:不直接调用静态方法,通过接口注入。
面向对象设计检查清单
- 每个类是否只负责一个明确的职责?
- 继承关系是否符合里氏替换原则?
- 接口是否足够小且内聚?
- 是否过度使用继承而非组合?
- 依赖关系是否通过接口注入而非硬编码?
7. 安全性
7.1 输入验证
-
所有外部输入必须验证:包括用户输入、API参数、文件上传等。
// 不推荐:未验证输入 public void createUser(String email) { User user = new User(email); userRepository.save(user); } // 推荐:验证邮箱格式 public void createUser(String email) { if (!EmailValidator.isValid(email)) { throw new IllegalArgumentException("无效邮箱: " + email); } User user = new User(email); userRepository.save(user); }
-
验证策略:
- 白名单优先:允许已知合法值,拒绝其他所有输入。
- 最小权限原则:限制输入长度、类型和范围。
7.2 防止SQL注入
-
使用参数化查询:避免直接拼接SQL语句。
# 不推荐(SQL注入风险) query = f"SELECT * FROM users WHERE username = '{username}'" cursor.execute(query) # 推荐 query = "SELECT * FROM users WHERE username = %s" cursor.execute(query, (username,))
-
ORM框架:优先使用ORM(如Entity Framework, Hibernate),自动处理参数化。
7.3 防止跨站脚本攻击(XSS)
-
输出编码:对所有用户生成的内容进行HTML/JavaScript编码。
// 前端示例:使用DOMPurify净化HTML const cleanHtml = DOMPurify.sanitize(userInput); document.getElementById('output').innerHTML = cleanHtml;
-
CSP(内容安全策略):限制页面可加载的资源来源。
Content-Security-Policy: default-src 'self'; script-src 'self' 'trusted-scripts.example.com';
7.4 防止跨站请求伪造(CSRF)
- CSRF令牌:为敏感操作(如表单提交)生成并验证令牌。
// Java Spring示例 @PostMapping("/transfer") public String transferFunds(@RequestParam String amount, @RequestParam String recipient, @RequestParam("_csrf") String csrfToken) { // 验证CSRF令牌 csrfTokenValidator.validate(csrfToken); // 处理转账 }
7.5 密码安全
-
哈希存储:使用强哈希算法(如BCrypt、Argon2)存储密码。
// .NET示例 string hashedPassword = BCrypt.Net.BCrypt.HashPassword(plainPassword); // 验证密码 bool isCorrect = BCrypt.Net.BCrypt.Verify(plainPassword, hashedPassword);
-
密码策略:
- 最小长度:至少12个字符。
- 复杂度:包含大小写字母、数字和特殊字符。
- 定期更换:每90天强制重置密码。
7.6 敏感数据处理
-
数据脱敏:不显示完整敏感信息(如信用卡号、身份证号)。
// 示例:显示部分信用卡号 public String maskCardNumber(String cardNumber) { if (cardNumber == null || cardNumber.length() <= 4) { return cardNumber; } return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4); }
-
数据传输:所有敏感数据通过HTTPS传输,禁用明文协议。
7.7 权限控制
-
基于角色的访问控制(RBAC):
# 示例:检查用户是否有管理员权限 def deleteUser(userId, currentUser): if not currentUser.hasRole("ADMIN"): raise PermissionDeniedException("需要管理员权限"); userRepository.delete(userId);
-
最小权限原则:用户仅拥有完成任务所需的最低权限。
7.8 错误处理与日志安全
-
错误信息:不向客户端暴露堆栈跟踪或内部路径。
// 不推荐 app.get('/user', (req, res) => { try { // 业务逻辑 } catch (err) { res.status(500).send(err.stack); // 暴露敏感信息 } }); // 推荐 app.get('/user', (req, res) => { try { // 业务逻辑 } catch (err) { logger.error("获取用户失败", err); res.status(500).send("服务器内部错误"); // 通用错误信息 } });
-
日志安全:
- 敏感信息(如密码、令牌)不记录。
- 日志文件访问控制严格。
7.9 安全漏洞扫描
- 定期扫描:使用工具(如OWASP ZAP、Nessus)进行漏洞扫描。
- 依赖检查:定期检查第三方库安全漏洞(如OWASP Dependency-Check)。
安全检查清单
- 是否验证了所有外部输入?
- 是否使用了参数化查询或ORM?
- 是否对用户生成内容进行了输出编码?
- 是否实现了CSRF防护?
- 是否安全存储和处理密码?
- 是否限制了敏感数据的访问和显示?
- 是否实现了细粒度的权限控制?
- 是否避免向客户端暴露敏感错误信息?
8. 性能优化
8.1 避免资源泄漏
-
及时释放资源:使用
using
(C#)、try-with-resources
(Java)或with
(Python)确保资源关闭。// 不推荐:手动关闭可能遗漏 public void readFile(String path) throws IOException { FileInputStream fis = new FileInputStream(path); try { // 使用fis读取文件 } finally { fis.close(); // 必须在finally中关闭 } } // 推荐:自动关闭资源 public void readFile(String path) throws IOException { try (FileInputStream fis = new FileInputStream(path)) { // 使用fis读取文件 } // 自动关闭 }
-
数据库连接池:使用连接池管理数据库连接,避免频繁创建和销毁。
8.2 内存管理最佳实践
-
避免内存泄漏:
- 静态集合中不保存长生命周期对象的引用。
- 及时解除监听器和回调引用。
-
大对象处理:
- 避免在循环中创建大对象(如大型数组)。
- 优先使用对象池复用重量级对象(如线程、数据库连接)。
8.3 算法复杂度优化
- 避免O(n²)算法:优先使用O(n)或O(log n)算法。
// 不推荐:双重循环(O(n²)) function findDuplicates(arr) { const duplicates = []; for (let i = 0; i < arr.length; i++) { for (let j = i + 1; j < arr.length; j++) { if (arr[i] === arr[j]) { duplicates.push(arr[i]); } } } return duplicates; } // 推荐:使用Set(O(n)) function findDuplicates(arr) { const seen = new Set(); const duplicates = new Set(); for (const item of arr) { if (seen.has(item)) { duplicates.add(item); } else { seen.add(item); } } return Array.from(duplicates); }
8.4 异步与并发处理
-
异步操作:I/O密集型任务使用异步编程提高吞吐量。
// C#示例:异步文件读取 public async Task<string> ReadFileAsync(string path) { using (var reader = File.OpenText(path)) { return await reader.ReadToEndAsync(); } }
-
线程池使用:计算密集型任务使用线程池避免创建过多线程。
// Java示例:使用ExecutorService ExecutorService executor = Executors.newFixedThreadPool(10); Future<String> future = executor.submit(() -> { // 执行耗时任务 return "结果"; });
-
锁粒度控制:避免大范围同步,减小锁的作用域。
# 不推荐:粗粒度锁 lock = threading.Lock() def process_data(): with lock: # 长时间操作(如网络请求) data = fetch_data() # 实际需要同步的操作 shared_resource.append(data) # 推荐:细粒度锁 def process_data(): data = fetch_data() # 非同步操作 with lock: shared_resource.append(data) # 仅同步关键操作
8.5 缓存策略
-
本地缓存:使用
ConcurrentHashMap
(Java)或MemoryCache
(.NET)缓存高频访问数据。private static final ConcurrentHashMap<String, User> userCache = new ConcurrentHashMap<>(); public User getUser(String id) { return userCache.computeIfAbsent(id, this::fetchUserFromDb); }
-
分布式缓存:使用Redis、Memcached缓存跨实例数据。
8.6 懒加载与预加载
-
懒加载:延迟初始化对象,直到首次使用。
class Config { constructor() { this._settings = null; } get settings() { if (!this._settings) { this._settings = this.loadSettings(); // 首次访问时加载 } return this._settings; } }
-
预加载:适用于已知后续会使用的数据(如启动时加载配置)。
8.7 数据库查询优化
-
索引优化:为高频查询字段添加索引。
-
批量操作:避免循环执行单条SQL,使用批量插入/更新。
// JDBC批量插入示例 try (Connection conn = dataSource.getConnection(); PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (name, email) VALUES (?, ?)")) { for (User user : users) { pstmt.setString(1, user.getName()); pstmt.setString(2, user.getEmail()); pstmt.addBatch(); } pstmt.executeBatch(); }
-
避免N+1查询:使用JOIN或批量查询替代循环查询。
性能检查清单
- 是否存在资源未及时释放的情况?
- 是否有内存泄漏风险(如静态集合持有对象引用)?
- 是否使用了高效的算法和数据结构?
- 是否将I/O密集型操作转为异步处理?
- 是否合理控制了锁的粒度?
- 是否对高频访问数据使用了缓存?
- 是否优化了数据库查询(索引、批量操作等)?
9. 测试与调试
9.1 单元测试原则
-
单一职责:每个测试方法只验证一个功能点或业务逻辑。
# 不推荐:一个测试方法验证多个功能 def test_user_creation_and_login():user = create_user("testuser", "test@example.com")assert user is not Noneresult = login(user.username, "password")assert result == "success"# 推荐:拆分测试 def test_user_creation():user = create_user("testuser", "test@example.com")assert user is not Nonedef test_user_login():user = create_user("testuser", "test@example.com")result = login(user.username, "password")assert result == "success"
-
独立运行:测试方法之间不能有依赖关系,每次运行都应独立执行。
-
可重复性:相同的输入应始终产生相同的输出,避免依赖外部可变状态(如实时时间、随机数)。
9.2 测试覆盖率
- 目标覆盖率:核心业务代码覆盖率应达到80%以上,非核心代码可适当降低。
- 覆盖范围:
- 分支覆盖:确保所有
if
、switch
等条件分支都被测试。 - 异常覆盖:验证方法抛出的所有异常情况。
- 边界条件覆盖:测试输入的最小值、最大值、空值等边界情况。
- 分支覆盖:确保所有
9.3 测试命名规范
- 清晰描述:测试方法名应明确表达测试内容和预期结果。
// 推荐 @Test public void whenUserExists_loginShouldSucceed() {// 测试逻辑 }@Test public void whenEmailIsInvalid_createUserShouldThrowException() {// 测试逻辑 }
9.4 调试信息与日志记录
-
日志级别:使用分级日志(DEBUG、INFO、WARN、ERROR)控制输出内容。
const winston = require('winston'); const logger = winston.createLogger({level: 'info',format: winston.format.json(),transports: [new winston.transport.Console()] });logger.debug('Debug信息,仅在开发环境使用'); logger.info('正常业务信息'); logger.warn('潜在问题警告'); logger.error('错误信息');
-
调试输出:
- 避免在生产环境中保留调试语句(如
console.log
)。 - 使用日志记录代替
console.log
,并通过配置文件控制日志输出。
- 避免在生产环境中保留调试语句(如
9.5 断言与前置条件
-
明确断言:使用断言库(如JUnit、Mocha、Pytest)验证结果。
// C#示例 using NUnit.Framework;[Test] public void Add_ShouldReturnCorrectSum() {int result = Calculator.Add(2, 3);Assert.AreEqual(5, result); }
-
前置条件检查:在测试方法开始前验证必要条件(如数据库连接、文件存在)。
import osdef test_read_file():file_path = "test.txt"assert os.path.exists(file_path), f"文件 {file_path} 不存在"content = read_file(file_path)assert len(content) > 0
9.6 集成测试与端到端测试
- 集成测试:验证多个模块或组件协同工作的正确性。
- 使用Mock对象隔离外部依赖(如数据库、第三方API)。
- 端到端测试:模拟用户真实操作流程,确保系统整体功能正常。
- 使用工具(如Selenium、Cypress)自动化UI测试。
9.7 测试数据管理
- 测试数据隔离:避免测试数据污染生产环境,使用独立的测试数据库或沙箱环境。
- 数据初始化:在测试前准备好必要的测试数据,测试后清理数据以保证独立性。
测试与调试检查清单
- 每个测试方法是否只验证一个功能点?
- 测试覆盖率是否达到目标要求?
- 测试方法命名是否清晰表达了测试内容?
- 是否使用分级日志记录信息?
- 断言是否准确验证了预期结果?
- 测试数据是否与生产环境隔离?
10. 版本控制
10.1 分支策略
- 主分支(Master/Main):
- 用于生产环境的稳定代码,仅接受通过测试的合并请求。
- 禁止直接在主分支提交代码,所有变更需通过其他分支合并。
- 开发分支(Develop):
- 作为日常开发的主分支,集成所有功能分支的代码。
- 定期合并到主分支,并打上版本标签(如
v1.0.0
)。
- 功能分支(Feature):
- 基于
Develop
分支创建,用于开发新功能或修复缺陷。 - 命名格式:
feature/xxx
(如feature/user-login
)。 - 开发完成后,通过合并请求(MR/PR)合并回
Develop
分支。
- 基于
- 发布分支(Release):
- 基于
Develop
分支创建,用于预发布准备(如版本号更新、文档整理)。 - 修复此分支上的紧急Bug后,同步合并到
Develop
和Master
分支。
- 基于
- 热修复分支(Hotfix):
- 基于
Master
分支创建,用于修复生产环境的紧急问题。 - 修复完成后,合并回
Master
和Develop
分支,并更新版本号。
- 基于
分支管理示例流程:
10.2 提交信息规范
- 格式要求:采用
<类型>(<作用域>): <描述>
结构。- 类型:
feat
(新功能)、fix
(修复Bug)、docs
(文档更新)、style
(格式调整)、refactor
(重构)等。 - 作用域:可选,标识代码模块(如
user
,order
)。 - 描述:简明扼要说明提交内容,不超过50个字符。
- 类型:
- 示例:
feat(user): 添加用户注册功能 fix(order): 修复订单支付金额计算错误 docs(api): 更新接口文档说明
- 多行描述:若需详细说明,可在单行描述后换行添加。
refactor(cache): 优化缓存读取逻辑1. 使用LRU算法替换旧策略 2. 减少缓存过期时的并发问题
10.3 代码审查流程
- 强制审查:所有合并请求必须通过至少一名团队成员审查。
- 审查内容:
- 代码是否符合编码规范。
- 逻辑正确性与潜在风险(如内存泄漏、安全漏洞)。
- 单元测试是否完整且通过。
- 提交信息是否清晰规范。
- 审查工具:使用GitHub、GitLab或Bitbucket的内置审查功能,或集成专门工具(如Gerrit)。
- 审查反馈:审查者需明确指出问题并提供修改建议,开发者修改后重新提交审查。
10.4 标签与版本管理
- 语义化版本(SemVer):采用
MAJOR.MINOR.PATCH
格式。MAJOR
:不兼容的API变更。MINOR
:向后兼容的功能新增。PATCH
:向后兼容的Bug修复。
- 标签示例:
v1.0.0 # 初始版本 v1.1.0 # 新增功能 v1.1.1 # 修复小Bug v2.0.0 # 重大架构调整
- 版本发布流程:
- 在
Release
分支更新版本号。 - 执行全面测试并修复发现的问题。
- 合并
Release
分支到Master
和Develop
分支。 - 在
Master
分支打上版本标签。
- 在
10.5 冲突解决策略
- 主动合并:定期将
Develop
分支合并到功能分支,减少冲突概率。 - 解决冲突:
- 使用
git mergetool
等工具辅助解决冲突。 - 冲突解决后,提交明确的冲突解决说明。
- 使用
- 避免强制推送:除非必要,禁止使用
git push --force
,防止覆盖他人提交。
10.6 协作规范
- 禁止直接推送主分支:所有变更必须通过合并请求进行。
- 分支清理:及时删除已合并的功能分支,保持仓库整洁。
- 备份与容灾:定期备份代码仓库,防止数据丢失。
版本控制检查清单
- 是否遵循标准的分支管理策略?
- 提交信息是否符合
<类型>(<作用域>): <描述>
格式? - 合并请求是否经过代码审查?
- 版本号是否遵循语义化版本规范?
- 冲突解决后是否提交了清晰的说明?
11. 文档与注释
11.1 函数与类注释模板
- 标准注释结构:包含功能描述、参数说明、返回值、异常抛出等信息。
/*** 计算两个整数的和** @param num1 第一个整数* @param num2 第二个整数* @return 两数之和* @throws IllegalArgumentException 当参数小于0时抛出异常*/ public int add(int num1, int num2) {if (num1 < 0 || num2 < 0) {throw new IllegalArgumentException("参数不能为负数");}return num1 + num2; }
- Web前端注释:使用JSDoc格式,支持类型标注。
/*** 格式化日期字符串* @param {string} dateStr - 原始日期字符串(格式:YYYY-MM-DD)* @param {string} format - 目标日期格式(如:MM/DD/YYYY)* @returns {string} 格式化后的日期字符串*/ function formatDate(dateStr, format) {// 实现逻辑 }
11.2 API文档生成
- 自动化工具:
- 后端:使用Swagger(OpenAPI)生成RESTful API文档,或通过工具(如Dokka、Javadoc)生成代码级API文档。
- 前端:使用TypeDoc生成TypeScript文档,或结合Storybook展示组件API。
- 文档内容:
- 接口描述、请求参数、响应格式、错误码说明。
- 示例代码和调用流程。
- 示例(Swagger):
paths:/users:get:summary: 获取用户列表parameters:- name: pagein: querydescription: 页码required: trueschema:type: integerresponses:200:description: 成功返回用户列表content:application/json:schema:type: arrayitems:$ref: '#/components/schemas/User'
11.3 技术文档维护
- 项目架构文档:记录系统整体架构、模块划分、依赖关系。
# 系统架构设计 ## 1. 分层架构 - 表示层:处理HTTP请求,使用Spring Boot(后端)/React(前端)。 - 应用层:协调业务逻辑,调用领域服务。 - 领域层:核心业务逻辑,包含实体和值对象。 - 基础设施层:数据库访问、消息队列等。
- 部署文档:说明环境配置、部署步骤、依赖组件版本。
# 部署指南 1. 安装Java 17和MySQL 8.0。 2. 执行数据库脚本 `init.sql`。 3. 打包项目:`mvn clean package`。 4. 启动服务:`java -jar target/app.jar`。
- 维护文档:记录常见问题解决方案、系统监控指标。
# 常见问题 - **问题**:服务启动失败,提示端口被占用。 **解决方案**:使用 `lsof -i:<端口号>` 查找占用进程并关闭。
11.4 注释更新原则
- 同步更新:代码修改后,对应注释和文档必须同步更新。
- 避免冗余:注释应补充代码逻辑难以表达的信息,而非重复代码。
// 不推荐:重复代码逻辑 let total = sum + value; // 将sum和value相加赋值给total// 推荐:解释业务意图 let total = sum + value; // 计算订单总价,包含商品金额和运费
- 废弃注释:及时删除不再生效的注释,避免误导。
11.5 团队协作规范
- 文档评审:重要文档(如架构设计、API文档)需通过团队评审。
- 统一工具:使用Confluence、GitBook等平台集中管理文档。
- 版本关联:文档版本与代码版本保持对应,便于追溯。
文档与注释检查清单
- 函数和类是否包含完整的注释(功能、参数、返回值、异常)?
- API文档是否自动生成并及时更新?
- 技术文档是否覆盖架构设计、部署流程和维护指南?
- 代码修改后,注释和文档是否同步更新?
- 团队是否使用统一的文档管理工具?
12. 兼容性与可维护性
12.1 跨平台/浏览器兼容性
-
前端浏览器兼容:
- 使用CSS前缀(如
-webkit-
,-moz-
)处理不同浏览器样式差异。 - 采用渐进增强或优雅降级策略,确保低版本浏览器基本可用。
/* 兼容不同浏览器的Flexbox */ display: -webkit-box; display: -ms-flexbox; display: flex;
- 利用
postcss-preset-env
或Autoprefixer
自动添加CSS前缀。 - 测试工具:使用BrowserStack、Sauce Labs或Chrome DevTools的设备模式模拟不同浏览器环境。
- 使用CSS前缀(如
-
后端跨平台部署:
- 避免依赖特定操作系统API(如Windows特有的文件路径格式)。
- 采用容器化技术(Docker)统一运行环境,确保在Linux、Windows、macOS上一致部署。
- 数据库连接:使用标准SQL语句,减少对数据库特定方言(如MySQL的
LIMIT
与Oracle的ROWNUM
)的依赖。
12.2 代码复用与DRY原则
- 提取公共代码:将重复逻辑封装为工具类、组件或函数。
// 不推荐:重复的日期格式化代码 const date1 = new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); const date2 = new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' });// 推荐:提取为公共函数 function formatDate() {return new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); } const date1 = formatDate(); const date2 = formatDate();
- 组件化设计:
- 前端:将UI拆分为可复用的组件(如Vue、React组件)。
- 后端:通过模块化(如Java的模块系统、C#的命名空间)划分功能。
12.3 技术债务管理
- 识别技术债务:通过代码审查、静态分析工具(如SonarQube)标记问题代码。
- 常见债务类型:重复代码、过度复杂逻辑、未修复的缺陷、过时的依赖库。
- 制定偿还计划:
- 记录债务清单,标注优先级和影响范围。
- 定期安排时间(如每个迭代周期)处理技术债务。
- 示例债务记录:
# 技术债务清单 | 编号 | 描述 | 优先级 | 影响范围 | 计划修复时间 | |------|--------------------------|--------|----------------|-------------| | 1 | 用户模块存在重复验证逻辑 | 高 | 用户注册、登录 | 2025-06-30 | | 2 | 第三方库lodash版本过低 | 中 | 全局 | 2025-07-15 |
12.4 可扩展性设计
- 接口与抽象层:通过接口或抽象类定义行为,便于扩展实现。
// 定义支付接口 interface PaymentGateway {boolean processPayment(double amount); }// 具体实现类 class PayPalGateway implements PaymentGateway {@Overridepublic boolean processPayment(double amount) {// 实现逻辑} }
- 配置驱动设计:将可变更的参数(如API地址、功能开关)提取到配置文件中。
# application.yml payment:gateway: paypalapiUrl: https://api.paypal.com
12.5 代码可读性与可理解性
- 变量与函数命名:使用清晰、完整的名称,避免使用模糊缩写。
- 逻辑拆分:将长方法拆分为多个小函数,每个函数专注单一任务。
# 不推荐:过长的函数 def process_order(order):# 验证订单# 计算总价# 处理支付# 生成发货单pass# 推荐:拆分逻辑 def validate_order(order):passdef calculate_total(order):passdef process_payment(order):passdef generate_shipment(order):pass
- 添加注释:对复杂逻辑、算法或设计决策添加注释说明。
兼容性与可维护性检查清单
- 前端代码是否兼容主流浏览器及低版本?
- 后端代码是否依赖特定操作系统或数据库方言?
- 是否存在重复代码或可复用的逻辑未提取?
- 技术债务是否记录并制定了修复计划?
- 代码结构是否便于扩展和修改?
13. 附录
13.1 术语表
术语 | 定义 |
---|---|
SRP(单一职责原则) | 每个类/模块只负责一项职责,降低复杂度和维护成本。 |
OCP(开闭原则) | 软件实体应对扩展开放,对修改关闭,通过抽象和多态实现。 |
DRY(不要重复自己) | 避免重复代码,通过复用和抽象提高效率。 |
KISS(保持简单) | 设计应尽量简洁,避免过度复杂。 |
CSP(内容安全策略) | 用于防止跨站脚本攻击(XSS)的HTTP头,限制页面可加载的资源来源。 |
CI/CD(持续集成/持续交付) | 开发流程中自动构建、测试和部署代码的实践。 |
ORM(对象关系映射) | 将数据库表映射为对象模型的工具,简化数据访问层开发。 |
AOP(面向切面编程) | 通过分离横切关注点(如日志、事务)提高代码模块化的编程范式。 |
13.2 参考资源
- 通用编程规范:
- 《Clean Code: A Handbook of Agile Software Craftsmanship》 - Robert C. Martin
- 《Design Patterns: Elements of Reusable Object-Oriented Software》 - Erich Gamma等
- 语言特定规范:
- Microsoft .NET Coding Conventions
- Google Java Style Guide
- Airbnb JavaScript Style Guide
- Android Code Style Guidelines
- 安全标准:
- OWASP Top Ten
- CWE/SANS Top 25 Most Dangerous Software Errors
- 工具与社区:
- 代码审查工具:GitHub Pull Requests, GitLab Merge Requests
- 静态分析工具:SonarQube, ESLint, Checkstyle
- 开发者社区:Stack Overflow, Reddit/r/learnprogramming