Nim 完全指南 / 24 最佳实践
第 24 章:最佳实践
24.1 命名规范
24.1.1 NEP 1 — Nim 命名规范
Nim 有一份官方的命名规范文档 NEP 1,核心规则如下:
| 元素 | 规范 | 示例 |
|---|
| 过程/方法/迭代器 | camelCase | getUserInfo, isValid |
| 类型 | PascalCase | UserInfo, HttpRequest |
| 常量 | PascalCase 或 SCREAMING_SNAKE | MaxSize, MAX_SIZE |
| 变量/字段 | camelCase | userName, totalCount |
| 模块文件 | 小写下划线 | my_module.nim, http_client.nim |
| 包名 | 小写或小写下划线 | jester, httpbeast |
| 枚举值 | camelCase | Red, LightGreen |
| 泛型参数 | 单大写字母 | T, U, K, V |
# ✅ 正确
type HttpRequest* = object
url*: string
headers*: seq[tuple[key, value: string]]
const DefaultTimeout* = 30
proc sendRequest*(req: HttpRequest): HttpResponse =
discard
# ❌ 错误
type http_request = object # 类型应为 PascalCase
proc Send_Request() = discard # 过程应为 camelCase
24.1.2 导出约定
# 公开 API:后缀 *
type User* = object
name*: string # 公开字段
age*: int
proc newUser*(name: string, age: int): User* =
User(name: name, age: age)
proc getName*(user: User): string =
user.name
# 内部实现:不导出
proc validateAge(age: int): bool =
age >= 0 and age <= 150
type CacheEntry = object # 模块内部使用
data: string
timestamp: float
24.2 代码风格
24.2.1 格式化
# 使用 nimpretty 格式化
nimpretty --indent:2 --maxLineLen:100 src/*.nim
24.2.2 推荐风格
import std/[
strutils,
sequtils,
tables
]
type
Config* = object
## 应用配置
host*: string ## 服务器地址
port*: int ## 服务器端口
debug*: bool ## 是否启用调试模式
proc newConfig*(host = "localhost", port = 8080): Config =
## 创建新配置
Config(host: host, port: port, debug: false)
proc formatAddress*(cfg: Config): string =
## 格式化为 host:port 格式
cfg.host & ":" & $cfg.port
# 使用 when 而不是嵌套 if
when isMainModule:
let cfg = newConfig(port = 3000)
echo cfg.formatAddress()
24.2.3 行长度与换行
# 长行换行
proc processVeryLongFunctionName(
param1: string,
param2: int,
param3: seq[float],
param4: bool = false
): Result[string, Error] =
# 函数体
discard
# 链式调用换行
let result = data
.filterIt(it > 0)
.mapIt(it * 2)
.sorted()
.deduplicate()
24.3 项目结构最佳实践
24.3.1 推荐目录结构
myproject/
├── src/
│ └── myproject/
│ ├── myproject.nim # 主模块(导出 API)
│ ├── types.nim # 类型定义
│ ├── config.nim # 配置管理
│ ├── utils.nim # 工具函数
│ └── private/ # 内部实现
│ ├── helpers.nim
│ └── cache.nim
├── tests/
│ ├── test_types.nim
│ ├── test_config.nim
│ └── test_utils.nim
├── examples/
│ ├── basic.nim
│ └── advanced.nim
├── docs/
├── config.nims
├── myproject.nimble
├── .gitignore
└── README.md
24.3.2 .gitignore
# Nim 编译产物
*.exe
*.out
nimcache/
nimblecache/
bin/
# IDE
*.code-workspace
.vscode/
.idea/
# 临时文件
*.swp
*.swo
*~
24.3.3 config.nims
# config.nims
switch("path", "src")
switch("nimcache", "nimcache")
when defined(release):
switch("opt", "speed")
switch("checks", "off")
else:
switch("debugger", "native")
switch("linedir", "on")
switch("hints", "on")
24.4 错误处理最佳实践
# ✅ 推荐:使用 Result 类型
import std/results
proc parseConfig(path: string): Result[Config, string] =
if not fileExists(path):
return err("File not found: " & path)
let content = try:
readFile(path)
except IOError as e:
return err("Read error: " & e.msg)
try:
ok(parseJson(content).to(Config))
except JsonParsingError as e:
return err("Parse error: " & e.msg)
# ✅ 推荐:有意义的错误信息
proc divide(a, b: float): Result[float, string] =
if b == 0.0:
err("Division by zero")
else:
ok(a / b)
# ❌ 不推荐:吞掉异常
# try: doSomething() except: discard
# ❌ 不推荐:过于宽泛的异常捕获
# try: doSomething() except CatchableError: discard
24.5 类型安全最佳实践
# ✅ 使用类型别名增加语义
type
UserId = int
Email = string
Money = float
proc sendEmail(to: Email, subject: string) =
discard
proc processPayment(userId: UserId, amount: Money) =
discard
# ✅ 使用范围类型限制值域
type
Percentage = range[0..100]
Port = range[0..65535]
Age = range[0..150]
# ✅ 使用 distinct 类型防止混淆
type
Celsius = distinct float
Fahrenheit = distinct float
proc toCelsius(f: Fahrenheit): Celsius =
Celsius((f.float - 32.0) * 5.0 / 9.0)
proc toFahrenheit(c: Celsius): Fahrenheit =
Fahrenheit(c.float * 9.0 / 5.0 + 32.0)
# 不能直接混用
let bodyTemp = Celsius(37.0)
# let wrong = bodyTemp + Fahrenheit(100.0) # ❌ 编译错误
24.6 性能最佳实践
# ✅ 使用值类型
type Point = object # 栈分配
x, y: float
# ✅ 预分配
var data = newSeqOfCap[int](10000)
# ✅ 使用 lent 避免拷贝
proc first[T](s: seq[T]): lent T = s[0]
# ✅ 使用 sink 转移所有权
proc consume(data: sink seq[int]) =
# data 的所有权转移到此过程
discard
# ✅ 使用 HashSet 做成员检查
import std/sets
let validCodes = toHashSet([200, 201, 204, 301, 302])
if code in validCodes:
discard
24.7 并发最佳实践
# ✅ 使用 Channel 进行线程通信
import std/threads
var channel: Channel[string]
channel.open(100)
# ✅ 使用 async/await 处理 I/O
import std/asyncdispatch
proc fetchData(url: string): Future[string] {.async.} =
let client = newAsyncHttpClient()
defer: client.close()
return await client.getContent(url)
# ✅ 避免共享可变状态
# 使用消息传递而不是共享内存
# ✅ 使用 Lock 保护共享数据
import std/locks
var sharedData: seq[int]
var lock: Lock
initLock(lock)
proc addToData(val: int) =
withLock(lock):
sharedData.add(val)
24.8 测试最佳实践
import std/unittest
# ✅ 测试应该独立、可重复
suite "User Service":
test "创建用户":
let svc = newUserService() # 每个测试新建
let user = svc.create("Alice", "[email protected]")
check user.name == "Alice"
test "查找用户":
let svc = newUserService()
discard svc.create("Alice", "[email protected]")
let found = svc.findByName("Alice")
check found.isSome
# ✅ 测试边界条件
suite "边界条件":
test "空输入":
check process("").len == 0
test "极大值":
check process($int.high).len > 0
test "特殊字符":
check process("hello\nworld\t!") != ""
# ✅ 测试错误情况
suite "错误处理":
test "无效输入":
expect ValueError:
discard parseInt("abc")
24.9 文档最佳实践
proc calculateDistance*(x1, y1, x2, y2: float): float =
## 计算两点之间的欧几里得距离
##
## 参数:
## x1, y1 — 第一个点的坐标
## x2, y2 — 第二个点的坐标
##
## 返回值:两点之间的距离
##
## 示例:
## ```nim
## echo calculateDistance(0, 0, 3, 4) # 5.0
## ```
sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
type
User* = object
## 用户对象
##
## 存储用户的基本信息,用于系统认证和授权
name*: string ## 用户名
email*: string ## 邮箱地址
age*: int ## 年龄,范围 0-150
24.10 代码审查清单
| 类别 | 检查项 |
|---|
| 命名 | 是否遵循 NEP 1 规范? |
| 导出 | 是否只导出必要的 API? |
| 类型 | 是否使用了合适的类型(范围、distinct)? |
| 错误 | 是否正确处理了所有错误情况? |
| 内存 | 是否避免了不必要的拷贝和分配? |
| 并发 | 是否线程安全?是否正确使用锁? |
| 测试 | 是否有足够的测试覆盖? |
| 文档 | 公开 API 是否有文档注释? |
| 格式 | 是否使用 nimpretty 格式化? |
本章小结
| 实践 | 要点 |
|---|
| 命名 | NEP 1 规范,camelCase 过程,PascalCase 类型 |
| 导出 | 只导出公开 API,使用 * 标记 |
| 类型 | 使用范围类型、distinct 类型增强安全 |
| 错误 | 使用 Result 类型,避免吞异常 |
| 测试 | 独立、可重复、覆盖边界条件 |
| 文档 | 公开 API 必须有文档注释 |
扩展阅读
← 上一章:JavaScript 后端 | 下一章:实战项目 →