强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Nim 完全指南 / 24 最佳实践

第 24 章:最佳实践

24.1 命名规范

24.1.1 NEP 1 — Nim 命名规范

Nim 有一份官方的命名规范文档 NEP 1,核心规则如下:

元素规范示例
过程/方法/迭代器camelCasegetUserInfo, isValid
类型PascalCaseUserInfo, HttpRequest
常量PascalCase 或 SCREAMING_SNAKEMaxSize, MAX_SIZE
变量/字段camelCaseuserName, totalCount
模块文件小写下划线my_module.nim, http_client.nim
包名小写或小写下划线jester, httpbeast
枚举值camelCaseRed, 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 后端 | 下一章:实战项目