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

news/2024/10/11 13:30:36/

目录

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语句由于其可能导致代码结构混乱,即使在大型函数中也应尽量避免使用。


http://www.ppmy.cn/news/1533122.html

相关文章

第1 章 第一节:基础语法

第1 章 第一节&#xff1a;基础语法 1.1书写规则 1.1.1关键字 在Java语言中&#xff0c;已经定义好的&#xff0c;具有一定的功能和作用的英文单词。所有的关键字都是小写的 在Java中总共有51个关键字&#xff0c;还有两个保留字const\goto. 常见的关键字&#xff1a; if…

华为设备所有查看命令以及其对应作用

display interface&#xff1a;查看接口的状态、配置和统计信息。display ip interface brief&#xff1a;简要查看接口的 IP 地址信息。display ip routing-table&#xff1a;查看路由表信息。display ospf peer&#xff1a;查看 OSPF 邻居的状态。display ospf routing&#…

快速选择算法--无序数组中寻找中位数 O(n)的算法及证明

一、排序算法 排序的算法是最容易想到的&#xff0c;但是即使是快排&#xff0c;平均复杂度也只有 O ( n log ⁡ n ) O(n \log n) O(nlogn)。 #include <iostream> #include <vector> #include <algorithm> using namespace std;double findMid(vector<…

【吊打面试官系列-MySQL面试题】为表中得字段选择合适得数据类型

大家好&#xff0c;我是锋哥。今天分享关于【为表中得字段选择合适得数据类型】面试题&#xff0c;希望对大家有帮助&#xff1b; 为表中得字段选择合适得数据类型 字段类型优先级: 整形>date,time>enum,char>varchar>blob,text 优先考虑数字类型&#xff0c;其次是…

DevExpress WinForms中文教程:Data Grid - 如何添加或删除行?

本教程介绍DevExpress WinForm的Data Grid控件UI元素和API&#xff0c;它们使您和最终用户能够添加或删除数据行。您将首选学习如何启用内置的数据导航器&#xff0c;然后学习如何使用Microsoft Outlook启发的New Item行添加新记录。最后教程将向您展示基本的API&#xff0c;它…

k8s 部署 prometheus

创建namespace prometheus-namespace.yaml apiVersion: v1 kind: Namespace metadata:name: ns-prometheus拉取镜像 docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/quay.io/prometheus/prometheus:v2.54.0prometheus配置文件configmap prometheus-configmap.yaml …

PHP 中,将 JSON 数据与二进制数据之间进行相互转化主要涉及两个步骤:

在 PHP 中&#xff0c;将 JSON 数据与二进制数据之间进行相互转化主要涉及两个步骤&#xff1a; 将 JSON 数据转换为二进制数据将二进制数据转换为 JSON 数据 1. 将 JSON 数据转换为二进制数据 要将 JSON 数据转换为二进制数据&#xff0c;首先需要将 JSON 数据解析成 PHP 数…

爬虫逆向学习(九):记录一个集cookie、请求参数、请求体、响应文本加密的站点反爬

此分享只用于学习用途&#xff0c;不作商业用途&#xff0c;若有冒犯&#xff0c;请联系处理 反爬前置信息 站点&#xff1a;aHR0cHM6Ly96d2Z3LmNxLmdvdi5jbi9pY2l0eS9pY2l0eS9lbmdpbmVlcmluZy9uYXZpZ2F0aW9u 接口&#xff1a;/icity/api-v2/cq.app.icity.engineering.Engine…