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

Erlang/OTP 完全指南 / 24 - 最佳实践

第 24 章:最佳实践 — 代码风格、OTP 设计原则、生产规范

本章汇总 Erlang/OTP 项目的代码风格规范、OTP 设计原则和生产环境最佳实践。


24.1 代码风格

24.1.1 命名规范

元素规范示例
模块名小写 + 下划线my_module, user_handler
函数名小写 + 下划线get_user, process_data
变量名大写开头 / 驼峰UserName, Acc, Result
Atom小写 + 下划线ok, error, user_not_found
大写 + 下划线?TIMEOUT, ?MAX_RETRIES
Record小写 + 下划线#user{}, #order{}

24.1.2 文件组织

%% ===== 属性声明(文件头)=====
-module(my_module).
-author("Developer").
-vsn("1.0.0").

%% ===== 编译选项 =====
-compile(export_all).  %% 仅在开发时使用

%% ===== 行为声明 =====
-behaviour(gen_server).

%% ===== 头文件包含 =====
-include_lib("eunit/include/eunit.hrl").

%% ===== 宏定义 =====
-define(TIMEOUT, 5000).
-define(LOG(Level, Msg), logger:log(Level, Msg)).

%% ===== 类型定义 =====
-type user() :: #{name := string(), age := integer()}.
-export_type([user/0]).

%% ===== 导出声明 =====
%% API
-export([start_link/0, get_user/1]).

%% Behaviour callbacks
-export([init/1, handle_call/3, handle_cast/2]).

%% ===== 记录定义 =====
-record(state, {
    users = #{} :: #{integer() => user()},
    last_id = 0 :: non_neg_integer()
}).

%% ===== API 实现 =====
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%% ===== Callback 实现 =====
init([]) ->
    {ok, #state{}}.

%% ===== 内部函数 =====

24.1.3 缩进与格式

%% 4 空格缩进(不是 Tab)
my_function(Arg1, Arg2) ->
    case Arg1 of
        pattern1 ->
            do_something(Arg2);
        pattern2 ->
            do_another_thing(Arg2)
    end.

%% 长函数调用换行
some_long_function(
    VeryLongArgument1,
    VeryLongArgument2,
    VeryLongArgument3
).

%% 条件表达式
if
    Condition1 -> Result1;
    Condition2 -> Result2;
    true -> DefaultResult
end.

%% 列表推导
Result = [
    transform(Item)
    || Item <- List,
       is_valid(Item)
].

24.2 OTP 设计原则

24.2.1 进程设计

原则说明
一个进程一个职责不要在单个进程中做太多事
使用 GenServer有状态进程用 GenServer,不要手写循环
必须有 Supervisor每个进程都放在监督树中
命名常驻进程使用 {local, Name} 注册
合理的重启策略根据依赖关系选择策略

24.2.2 应用结构

my_app/
├── src/
│   ├── my_app_app.erl      ← Application 回调
│   ├── my_app_sup.erl       ← 顶层 Supervisor
│   ├── my_app_server.erl    ← 核心服务
│   ├── my_app_handler.erl   ← 请求处理
│   └── my_app_utils.erl     ← 工具函数(纯函数)
├── include/
│   └── my_app.hrl           ← 公开的 record/macro/type
├── test/
│   └── my_app_test.erl
├── config/
│   ├── sys.config
│   └── vm.args
└── priv/
    └── static/              ← 静态资源

24.2.3 API 设计模式

%% ✅ 好的 API:简洁明确
-module(user_service).
-export([create/1, get/1, update/2, delete/1]).

-spec create(#{name := string(), age := integer()}) -> {ok, user()} | {error, term()}.
create(#{name := _, age := _} = Params) ->
    gen_server:call(?MODULE, {create, Params}).

-spec get(integer()) -> {ok, user()} | {error, not_found}.
get(Id) ->
    gen_server:call(?MODULE, {get, Id}).

%% ❌ 不好的 API:模糊、职责不清
-export([do_stuff/2, handle/3, process/4]).

24.3 生产规范

24.3.1 日志

%% 使用 OTP 21+ 的 logger 模块
-include_lib("kernel/include/logger.hrl").

%% 不同级别的日志
?LOG_DEBUG("Debug message: ~p", [Data]).
?LOG_INFO("User ~p logged in", [UserId]).
?LOG_WARNING("High memory usage: ~p%", [Usage]).
?LOG_ERROR("Database connection failed: ~p", [Reason]).

%% logger 配置
%% config/sys.config
[
    {kernel, [
        {logger, [
            {handler, default, logger_std_h, 
             #{level => info,
               config => #{file => "log/app.log"}}},
            {handler, error_log, logger_std_h,
             #{level => error,
               config => #{file => "log/error.log"}}}
        ]}
    ]}
].

24.3.2 错误处理

%% ✅ 好的错误处理:明确的错误类型
-spec divide(number(), number()) -> {ok, float()} | {error, division_by_zero}.
divide(_, 0) -> {error, division_by_zero};
divide(A, B) -> {ok, A / B}.

%% ✅ 使用 try/catch 处理外部调用
safe_file_read(Path) ->
    try file:read_file(Path) of
        {ok, Data} -> {ok, Data};
        {error, Reason} -> {error, {file_error, Reason}}
    catch
        C:E -> {error, {exception, C, E}}
    end.

%% ❌ 不要忽略错误
{ok, Data} = file:read_file("config.txt").  %% 失败时崩溃

24.3.3 配置管理

%% 集中配置模块
-module(app_config).
-export([get/1, get/2]).

get(Key) ->
    application:get_env(my_app, Key).

get(Key, Default) ->
    application:get_env(my_app, Key, Default).

%% 使用
Port = app_config:get(http_port, 8080).

24.3.4 监控与告警

%% 进程监控
%% 1. 使用 Observer
observer:start().

%% 2. 进程内存监控
-spec check_process_memory() -> [{pid(), integer()}].
check_process_memory() ->
    Procs = [{P, process_info(P, memory)} || P <- erlang:processes()],
    [{P, M} || {P, {_, M}} <- Procs, M > 100000000].  %% > 100MB

%% 3. 消息队列监控
-spec check_mailboxes() -> [{pid(), integer()}].
check_mailboxes() ->
    Procs = [{P, process_info(P, message_queue_len)} || P <- erlang:processes()],
    [{P, L} || {P, {_, L}} <- Procs, L > 1000].

%% 4. 定期检查
start_monitor() ->
    spawn_link(fun() -> monitor_loop() end).

monitor_loop() ->
    timer:sleep(60000),  %% 每分钟
    case check_mailboxes() of
        [] -> ok;
        Blocked ->
            logger:warning("Processes with high mailbox: ~p", [Blocked])
    end,
    monitor_loop().

24.4 代码审查清单

24.4.1 功能正确性

检查项说明
模式匹配覆盖所有 case/if 是否有默认分支
边界条件空列表、零值、负数处理
错误处理外部调用是否有 try/catch
类型规范公开函数是否有 -spec

24.4.2 并发安全

检查项说明
GenServer 正确性回调函数返回格式是否正确
超时处理gen_server:call 是否有超时
消息处理handle_info 是否处理未知消息
资源清理terminate 是否清理资源

24.4.3 性能

检查项说明
尾递归长列表处理是否使用尾递归
ETS 使用高频读写是否用 ETS
Binary vs List大字符串是否用 binary
IO List字符串拼接是否用 IO List

24.5 项目模板

## 项目初始化模板
rebar3 new release my_project

## 添加常用依赖
## rebar.config
{deps, [
    {cowboy, "2.12.0"},     %% HTTP
    {jsx, "3.1.0"},         %% JSON
    {gun, "2.1.0"},         %% HTTP 客户端
    {jiffy, "1.1.1"},       %% JSON (NIF)
    {poolboy, "1.5.2"},     %% 连接池
    {meck, "0.9.2"},        %% Mock (test)
    {proper, "1.4.0"}       %% Property test (test)
]}.

## 常用命令
rebar3 compile          %% 编译
rebar3 eunit            %% 单元测试
rebar3 ct               %% 集成测试
rebar3 dialyzer         %% 类型检查
rebar3 as prod release  %% 生产构建
rebar3 shell            %% 开发 Shell

24.6 注意事项

⚠️ 反模式

反模式说明正确做法
process_flag(trap_exit, true) 滥用每个进程都 trap_exit只在需要清理时使用
erlang:get/put使用进程字典存储状态使用 GenServer State
io:format 调试到处打印使用 logger
巨型模块一个模块 1000+ 行拆分为多个模块
全局可变状态ETS public + 随意写封装在 GenServer 中
同步调用链A -> B -> C -> D改为异步消息传递

24.7 扩展阅读


上一章:23 - Web 开发 下一章:25 - 实战项目