函数都写不好,确实有些丢人。如何把函数写的整洁呢?看了会书深有启发。
这里使用C++语言来作为示例,但对其他语言的函数书写也有借鉴意义。
写函数的第一规则是要短小。第二条规则是还要更短小。
下面是两段功能一致的代码,分别展示了不同的实现方式:
第一段代码将所有的实现都写在了一个函数里,实现了一个简单的计算平均值的功能:
include <iostream>
#include <vector>
using namespace std;
double average(vector<double> v) {
double sum = 0;
for (int i = 0; i < v.size(); i++) {
sum += v[i];
}
return sum / v.size();
}
int main() {
vector<double> v{1, 2, 3, 4, 5};
double avg = average(v);
cout << "The average is: " << avg << endl;
return 0;
}
第二段代码对第一段代码进行了抽象,将复用的代码抽取成了一个共用的函数sum
,然后在average
函数中调用sum
函数,实现了相同的功能。这样做的好处是可以减少代码量,提高代码的可读性和可维护性。
#include <iostream>
#include <vector>
using namespace std;
double sum(vector<double> v) {
double s = 0;
for (int i = 0; i < v.size(); i++) {
s += v[i];
}
return s;
}
double average(vector<double> v) {
return sum(v) / v.size();
}
int main() {
vector<double> v{1, 2, 3, 4, 5};
double avg = average(v);
cout << "The average is: " << avg << endl;
return 0;
}
这两段代码实现的功能是一致的,但是第二段代码通过抽象函数,使代码更加简洁易懂,也更加容易维护。
只做一件事
下面的示例描述了把大象放进冰箱的过程:
function putElephantInFridge()
openFridgeDoor()
moveElephantToDoor()
pushElephantIntoFridge()
closeFridgeDoor()
done()
function openFridgeDoor()
// code to open fridge door
print("Opening fridge door")
function moveElephantToDoor()
// code to move elephant to fridge door
print("Moving elephant to fridge door")
function pushElephantIntoFridge()
// code to push elephant into fridge
print("Pushing elephant into fridge")
function closeFridgeDoor()
// code to close fridge door
print("Closing fridge door")
function done()
// code to signal completion
print("Elephant is now in the fridge")
注意,判断函数是否只做了一件事,就是看其是否能再拆出一个函数。
每个函数一个抽象层级
要确保函数只做一件事,函数中的语句都要在同一抽象层级上。下面用番茄炒蛋的伪代码来演示这一思想。
function cookTomatoEgg() {
// 第一层函数调用,准备工作
prepareIngredients();
// 开始烹饪
startCooking();
// 上菜
serveDish();
}
// 第二层函数调用,开始烹饪
function startCooking() {
// 开始热锅
heatPan();
// 加入蛋液煎炒
fryEgg();
// 加入西红柿煮炒
fryTomato();
// 加调味料
addSeasoning();
// 完成烹饪
finishCooking();
}
// 第三层函数调用,开始热锅
function heatPan() {
// 设置炉灶温度
setStoveTemperature(6);
// 加油热锅
addOil();
// 等待锅变热
waitPanHeat();
}
switch语句
确保每个switch语句都埋藏在较低的抽象层级,而且永远不重复。
什么是把switch语句放在较高抽象层级
Money calculatePay(unsigned int type)
{
if(type > 3) {std::cout << "invalid employee type!" << std::endl;}
switch (type)
{
case COMMISSIONED:
return calculateCommissionedPay(type);
case HOURLY:
return calculateHourlyPay(type);
case SALARIED:
return calculateSalariedPay(type);
default:
throw std::invalid_argument("Invalid employee type.");
}
}
void deliverPay(unsigned int type)
{
if(type > 3) {std::cout << "invalid employee type!" << std::endl;}
switch (type)
{
case COMMISSIONED:
return deliverCommissionedPay(type);
case HOURLY:
return deliverHourlyPay(type);
case SALARIED:
return deliverSalariedPay(type);
default:
throw std::invalid_argument("Invalid employee type.");
}
}
什么是把switch语句放在较低抽象层级
#include <iostream>
#include <string>
using namespace std;
class Employee {
public:
virtual bool isPayday() = 0;
virtual double calculatePay() = 0;
virtual void deliverPay(double pay) = 0;
};
class CommissionEmployee : public Employee {
public:
bool isPayday() override {
// 判断是否为发薪日
return true;
}
double calculatePay() override {
// 计算薪资
return 1000.0;
}
void deliverPay(double pay) override {
// 发放薪资
cout << "Commission Employee has been paid " << pay << " dollars." << endl;
}
};
class HourlyEmployee : public Employee {
public:
bool isPayday() override {
// 判断是否为发薪日
return true;
}
double calculatePay() override {
// 计算薪资
return 500.0;
}
void deliverPay(double pay) override {
// 发放薪资
cout << "Hourly Employee has been paid " << pay << " dollars." << endl;
}
};
class SalariedEmployee : public Employee {
public:
bool isPayday() override {
// 判断是否为发薪日
return true;
}
double calculatePay() override {
// 计算薪资
return 800.0;
}
void deliverPay(double pay) override {
// 发放薪资
cout << "Salaried Employee has been paid " << pay << " dollars." << endl;
}
};
class EmployeeFactory {
public:
Employee* createEmployee(string employeeType) {
Employee* employee = nullptr;
switch (employeeType) {
case "CommissionEmployee":
employee = new CommissionEmployee();
break;
case "HourlyEmployee":
employee = new HourlyEmployee();
break;
case "SalariedEmployee":
employee = new SalariedEmployee();
break;
default:
throw std::invalid_argument("Invalid employee type.");
}
};
int main() {
EmployeeFactory* employeeFactory = new EmployeeFactory();
Employee* employee1 = employeeFactory->createEmployee("CommissionEmployee");
if (employee1->isPayday()) {
double pay = employee1->calculatePay();
employee1->deliverPay(pay);
}
delete employee1;
Employee* employee2 = employeeFactory->createEmployee("HourlyEmployee");
if (employee2->isPayday()) {
double pay = employee2->calculatePay();
employee2->deliverPay(pay);
}
delete employee2;
Employee* employee3 = employeeFactory->createEmployee("SalariedEmployee");
if (employee3->isPayday()) {
double pay = employee3->calculatePay();
employee3->deliverPay(pay);
}
delete employee3;
delete employeeFactory;
return 0;
}
使用描述性的名称
函数名称需要较好地描述函数做的事情。
函数越短小,功能越集中,就越便于取个好名字。
别害怕长名称。
命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。
函数参数
最理想的参数数量是零,其次是一个,再次是两个,应尽量避免多参数函数。
多参数函数难以编写测试用例。
StringBuffer transform(StringBuffer in);
void transform(StringBuffer in, StringBuffer out);
2.传入True/False说明函数不止干了一件事。
3.如果参数太多可将其组合成结构体或类。
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
无副作用
函数内只做函数名描述的事情,不要隐藏地做其他事情。
分隔指令与查询
指令和查询混淆的示例:
bool set(string attribute, string value);
if(set("username", "unclebob"));
指令和查询分开的示例:
if(attributeExists("username"))
{
setAttribute("username", "unclebob");
}
使用异常替代返回错误码
在if语句中把指令当作表达式使用的示例(这种方式违背了分隔指令与查询思想):
使用异常替代返回错误码
错误码可能由一个共有的类型去管理。当新增错误码时需要重新编译所有依赖该错误码类型的文件。
例如:
enum Error{
OK,
INVALID,
NO_SUCH,
LOCKED,
OUT_OF_RESOURCES
};
使用异常替代错误码,新异常就可以从异常类派生出来,编译时不影响其他文件。
throw std::runtime_error("invalid parameters");
抽离try/catch代码块
将错误处理代码抽象成单个函数,避免try/catch中写过多的语句。
错误处理就是一件事
错误处理就是一件事,这意味着可以实现一个专门处理错误的函数。这个函数里只有try/catch结构。
别重复自己
将重复的代码抽象到公有函数或基类中,从而避免冗余。
写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。 初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。
我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称 是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。
然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时 我还拆散类。同时保持测试通过。
最后,遵循本章列出的规则,我组装好这些函数。
我并不从一开始就按照规则写函数。我想没人做得到。
以上总结自 《代码整洁之道》第三章--函数。
By the way, 示例代码使用ChatGPT生成。哈哈哈!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。