代码整洁之道 — 2 函数规范

目录

1 简短

2 switch语句

3 函数参数

4 无副作用

5 结构化编程


1 简短

函数应该保持简短,以提高代码的可读性和可维护性。简短的函数通常在20行以内,并且每个函数只做一件事,并清晰地表达其目的。函数应该保持单一职责,只处理一个抽象层级上的任务。混合不同层级的操作会导致代码难以理解。为了提高代码的清晰度,我们应该按照自顶向下的顺序组织函数,每个函数都应该调用下一个抽象层级的函数,形成清晰的阅读路径。

#include <stdio.h>
#include <string.h>

// 高层抽象:渲染页面
void renderPage(char* buffer, int bufferSize) {
    setupPage(buffer, bufferSize);
    addContent(buffer, bufferSize);
    teardownPage(buffer, bufferSize);
}

// 中层抽象:设置页面
void setupPage(char* buffer, int bufferSize) {
    // ...
}

// 低层抽象:添加页面内容
void addContent(char* buffer, int bufferSize) {
    // ...
}

// 低层抽象:拆除页面
void teardownPage(char* buffer, int bufferSize) {
    // ...
}

int main() {
    char page[1024];
    renderPage(page, sizeof(page));
    printf("%s\n", page);
    return 0;
}

2 switch语句

Switch语句通常很难保持短小和单一职责,因为它们往往需要处理多种情况。为了解决这个问题,可以将Switch语句隐藏在较低的抽象层级中。这样,Switch语句只出现一次,且不会被系统其他部分直接访问。

例如,在一个工资支付系统中,我们可能会根据不同的员工类型(如小时工、月薪员工、佣金员工)来计算工资。为了保持代码的清晰性和单一职责,我们可以将Switch语句隐藏在一个工厂方法中,该方法负责创建不同类型的员工对象。

#include <stdio.h>
#include <stdlib.h>

typedef enum {
    HOURLY,
    SALARIED,
    COMMISSIONED
} EmployeeType;

typedef struct {
    EmployeeType type;
    double hoursWorked;
    double salary;
    double commissionRate;
    double salesAmount;
} Employee;


typedef struct {
    double hourlyWage;
    double salaryPerMonth;
    double commission;
} PayDetails;

// 计算工资的函数声明
PayDetails calculateHourlyPay(double hourlyWage, double hoursWorked);
PayDetails calculateSalariedPay(double salaryPerMonth);
PayDetails calculateCommissionedPay(double commissionRate, double salesAmount);

// 工厂方法,根据员工类型计算工资
PayDetails calculatePay(Employee employee) {
    switch (employee.type) {
        case HOURLY:
            return calculateHourlyPay(employee.hourlyWage, employee.hoursWorked);
        case SALARIED:
            return calculateSalariedPay(employee.salaryPerMonth);
        case COMMISSIONED:
            return calculateCommissionedPay(employee.commissionRate, employee.salesAmount);
        default:
            return (PayDetails){0, 0, 0}; // 默认返回零工资
    }
}


// 实现计算小时工资的函数
PayDetails calculateHourlyPay(double hourlyWage, double hoursWorked) {
    return (PayDetails){hourlyWage * hoursWorked, 0, 0};
}

// 实现计算月薪的函数
PayDetails calculateSalariedPay(double salaryPerMonth) {
    return (PayDetails){0, salaryPerMonth, 0};
}

// 实现计算佣金工资的函数
PayDetails calculateCommissionedPay(double commissionRate, double salesAmount) {
    return (PayDetails){0, 0, commissionRate * salesAmount};
}

int main() {
    Employee employee = {COMMISSIONED, 0, 0, 0.1, 10000};
    PayDetails pay = calculatePay(employee);
    printf("Hourly Pay: %.2f\n", pay.hourlyWage);
    printf("Salary: %.2f\n", pay.salaryPerMonth);
    printf("Commission: %.2f\n", pay.commission);
    return 0;
}

3 函数参数

一元函数的普遍形式:一元函数应该清晰地表达其意图,要么询问关于参数的问题,要么对参数进行操作并返回结果。应该避免使用输出参数进行转换,而应该使用返回值。

#include <stdio.h>
#include <stdbool.h>

// 检查文件是否存在
bool fileExists(const char* filename) {
    // 假设的文件存在检查逻辑
    return true;
}

// 打开文件
FILE* fileOpen(const char* filename) {
    // 假设的文件打开逻辑
    return fopen(filename, "r");
}

int main() {
    bool exists = fileExists("MyFile.txt");
    printf("File exists: %s\n", exists ? "Yes" : "No");

    FILE* file = fileOpen("MyFile.txt");
    if (file) {
        printf("File opened successfully.\n");
        fclose(file);
    }
    return 0;
}

标识参数:避免使用布尔值作为标识参数,因为这会增加函数的复杂性,如果必须使用,应该考虑将函数拆分为两个或更多函数。

#include <stdio.h>

// 重构前:使用布尔标识参数
void render(bool isSuite) {
    if (isSuite) {
        // 渲染套件
    } else {
        // 渲染单个测试
    }
}

// 重构后:拆分为两个函数
void renderForSuite() {
    // 渲染套件
}

void renderForSingleTest() {
    // 渲染单个测试
}

int main() {
    renderForSuite();
    renderForSingleTest();
    return 0;
}

多元函数:多元函数需要更多的上下文来理解参数之间的关系,应该尽量避免或者通过将参数组合成对象来简化。

#include <stdio.h>
#include <stdlib.h>

// 比较两个值
void assertEquals(const char* message, int expected, int actual) {
    if (expected != actual) {
        printf("%s: expected %d but got %d\n", message, expected, actual);
    }
}

int main() {
    assertEquals("Test failed", 10, 20);
    return 0;
}

参数对象:如果函数看起来需要多个参数,这通常是一个信号,表明应该将这些参数封装成一个对象。这样可以减少参数的数量,提高代码的可读性。

#include <stdio.h>

// 定义一个结构体来封装多个参数
typedef struct {
    int year;
    int month;
    int day;
} Date;

// 函数声明,现在只接受一个参数
void printDate(const Date* date);

int main() {
    // 创建一个日期对象
    Date today = {2024, 5, 19};

    // 调用函数,只传递一个参数
    printDate(&today);
    return 0;
}

// 函数定义,接受一个日期结构体作为参数
void printDate(const Date* date) {
    printf("Today's date: %d-%d-%d\n", date->year, date->month, date->day);
}

动词与关键字:函数名和参数应该形成清晰的动词/名词对,这有助于解释函数的意图和参数的顺序,关键字形式的函数名可以减少记忆参数顺序的负担。

#include <stdio.h>

// 使用动词和关键字
void assertExpectedEqualsActual(int expected, int actual) {
    if (expected != actual) {
        printf("Assertion failed: expected %d, actual %d\n", expected, actual);
    }
}

int main() {
    assertExpectedEqualsActual(5, 3);
    return 0;
}

4 无副作用

函数应该避免副作用,即在执行过程中对外部状态进行修改,如改变全局变量或输入参数的值。副作用会破坏函数的纯粹性,导致代码难以理解和维护。

在下面示例中,checkPassword 函数只负责验证用户名和密码是否匹配,它不改变任何外部状态。如果需要初始化会话,应该在 main 函数中单独处理,而不是在密码检查函数中。这样的设计避免了副作用,使得 checkPassword 函数更加清晰和可靠。

#include <stdio.h>
#include <string.h>

// 定义一个用户结构体
typedef struct {
    char username[50];
    char hashedPassword[50]; // 存储加密后的密码
} User;

// 定义一个密码验证器结构体
typedef struct {
    // 加密器的实现细节
} Cryptographer;

// 密码验证器实例
Cryptographer cryptographer;

// 检查密码是否正确,不产生副作用
int checkPassword(const char* username, const char* password) {
    User user;
    // 假设从数据库或其他来源获取用户信息
    strcpy(user.username, username);
    strcpy(user.hashedPassword, "hashed_password"); // 假设的加密密码

    // 使用加密器验证密码
    if (strcmp(cryptographer.decrypt(user.hashedPassword), password) == 0) {
        return 1; // 密码正确
    }
    return 0; // 密码错误
}

// 解密密码的函数(假设实现)
char* decrypt(const char* hashedPassword) {
    // 返回明文密码
    return (char*)"password";
}

int main() {
    char username[50] = "user1";
    char password[50] = "password";

    // 调用checkPassword,不会产生副作用
    if (checkPassword(username, password)) {
        printf("Login successful.\n");
        // 可以在这里初始化会话,而不是在checkPassword中
    } else {
        printf("Login failed.\n");
    }
    return 0;
}

5 结构化编程

结构化编程强调每个函数只应有一个入口和一个出口,避免使用break、continue和goto语句。虽然这种规范对于大型函数有益,有助于提高代码的清晰性和可维护性,但在小型函数中可能过于严格。因此,在保持函数简短的情况下,偶尔使用return、break或continue语句是可以接受的,它们可以提高代码的表达力。然而,goto语句由于其可能导致代码结构混乱,即使在大型函数中也应尽量避免使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

几度春风里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值