前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >真丢人,函数都不会写?

真丢人,函数都不会写?

原创
作者头像
首飞
修改2023-05-31 21:32:47
1350
修改2023-05-31 21:32:47
举报
文章被收录于专栏:ROS2ROS2

函数都写不好,确实有些丢人。如何把函数写的整洁呢?看了会书深有启发。

这里使用C++语言来作为示例,但对其他语言的函数书写也有借鉴意义。

函数书写的原则

写函数的第一规则是要短小。第二条规则是还要更短小。

下面是两段功能一致的代码,分别展示了不同的实现方式:

第一段代码将所有的实现都写在了一个函数里,实现了一个简单的计算平均值的功能:

代码语言: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函数,实现了相同的功能。这样做的好处是可以减少代码量,提高代码的可读性和可维护性。

代码语言:c++
复制
#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;
}

这两段代码实现的功能是一致的,但是第二段代码通过抽象函数,使代码更加简洁易懂,也更加容易维护。

只做一件事

下面的示例描述了把大象放进冰箱的过程:

代码语言:c++
复制
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")

注意,判断函数是否只做了一件事,就是看其是否能再拆出一个函数。

每个函数一个抽象层级

要确保函数只做一件事,函数中的语句都要在同一抽象层级上。下面用番茄炒蛋的伪代码来演示这一思想。

代码语言:c++
复制
function cookTomatoEgg() {
    // 第一层函数调用,准备工作
    prepareIngredients();
    
    // 开始烹饪
    startCooking();
    
    // 上菜
    serveDish();
}

// 第二层函数调用,开始烹饪
function startCooking() {

    // 开始热锅
    heatPan();

    // 加入蛋液煎炒
    fryEgg();

    // 加入西红柿煮炒
    fryTomato();

    // 加调味料
    addSeasoning();

    // 完成烹饪
    finishCooking();
}

// 第三层函数调用,开始热锅
function heatPan() {
    // 设置炉灶温度
    setStoveTemperature(6);

    // 加油热锅
    addOil();

    // 等待锅变热
    waitPanHeat();
}

switch语句

确保每个switch语句都埋藏在较低的抽象层级,而且永远不重复。

什么是把switch语句放在较高抽象层级

代码语言:c++
复制
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语句放在较低抽象层级

代码语言:c++
复制
#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;
}

使用描述性的名称

函数名称需要较好地描述函数做的事情。

函数越短小,功能越集中,就越便于取个好名字。

别害怕长名称。

命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。

函数参数

最理想的参数数量是零,其次是一个,再次是两个,应尽量避免多参数函数。

多参数函数难以编写测试用例。

  1. 单参数函数
  • 有输入参数无输出参数。程序将函数看作是一个事件,使用该参数修改系统状态。
  • 有输入参数有输出参数。如果函数要对输入参数进行转换操作,转换结果要体现为返回值。
代码语言:c++
复制
   StringBuffer transform(StringBuffer in);

   void transform(StringBuffer in, StringBuffer out);

2.传入True/False说明函数不止干了一件事。

3.如果参数太多可将其组合成结构体或类。

代码语言:c++
复制
   Circle makeCircle(double x, double y, double radius);

   Circle makeCircle(Point center, double radius);

无副作用

函数内只做函数名描述的事情,不要隐藏地做其他事情。

分隔指令与查询

指令和查询混淆的示例:

代码语言:c++
复制
bool set(string attribute, string value);
if(set("username", "unclebob"));

指令和查询分开的示例:

代码语言:c++
复制
if(attributeExists("username"))
{
    setAttribute("username", "unclebob");
}

使用异常替代返回错误码

在if语句中把指令当作表达式使用的示例(这种方式违背了分隔指令与查询思想):

使用异常替代返回错误码

错误码可能由一个共有的类型去管理。当新增错误码时需要重新编译所有依赖该错误码类型的文件。

例如:

代码语言:c++
复制
enum Error{
    OK,
    INVALID,
    NO_SUCH,
    LOCKED,
    OUT_OF_RESOURCES
};

使用异常替代错误码,新异常就可以从异常类派生出来,编译时不影响其他文件。

代码语言:c++
复制
throw std::runtime_error("invalid parameters");

抽离try/catch代码块

将错误处理代码抽象成单个函数,避免try/catch中写过多的语句。

错误处理就是一件事

错误处理就是一件事,这意味着可以实现一个专门处理错误的函数。这个函数里只有try/catch结构。

别重复自己

将重复的代码抽象到公有函数或基类中,从而避免冗余。

如何写出这样的函数

写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。 初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。

我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称 是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。

然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时 我还拆散类。同时保持测试通过。

最后,遵循本章列出的规则,我组装好这些函数。

我并不从一开始就按照规则写函数。我想没人做得到。

以上总结自 《代码整洁之道》第三章--函数。

By the way, 示例代码使用ChatGPT生成。哈哈哈!


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 函数书写的原则
  • 如何写出这样的函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档