C编程:如何在不失去清晰度的情况下避免代码重复

时间:2013-11-13 20:12:52

标签: c code-duplication deduplication

编辑:感谢所有回复者。我应该在我的原始帖子中提到我不允许更改这些函数的任何规范,因此使用断言和/或允许取消引用NULL的解决方案是不可能的。 考虑到这一点,我认为我要么使用函数指针,要么只是保留复制。为了清楚起见,我希望这次避免使用函数指针。

原: 我试图避免代码重复而不会失去清晰度。 通常在进行特定任务(Uni - undergrad)时,我会认识到这些函数模式会返回,但并不总是使用"伟大的工作"溶液..

你们有什么建议我应该做的(用函数,宏等指针)这三个C函数以相同的方式检查某些的参数,以使检查更加模块化(它应该更模块化,对吧?)?

BTW这些是直接从HW分配中获取的,因此它们的功能细节与我的问题无关,只有在函数顶部检查的参数。

 teamIsDuplicateCoachName(Team team, bool* isDuplicate) {
    TeamResult result = TEAM_SUCCESS;
    if (!team || !isDuplicate) {
        result = TEAM_NULL_ARGUMENT;
    } else if (teamEmpty(team)) {
        result = TEAM_IS_EMPTY;
    } else {
        for (int i = 0; i < team->currentFormations; ++i) {
            if (teamIsPlayerInFormation(team->formations[i], team->coach)) {
                *isDuplicate = true;
                break;
            }
        }
    }
    return result;
}

TeamResult teamGetWinRate(Team team, double* winRate) {
    TeamResult result = TEAM_SUCCESS;
    if (!team || !winRate) {
        result = TEAM_NULL_ARGUMENT;
    } else {
        int wins = 0, games = 0;
        for (int i = 0; i < team->currentFormations; ++i) {
            Formation formation = team->formations[i];
            if (formationIsComplete(formation)) {
                games += formation->timesPlayed;
                wins += formation->timesWon;
            }
        }
        double win = ( games == 0 ) ? 0 : (double) wins / games;
        assert(win >= 0 && win <= 1);
        *winRate = win;
    }
    return result;
}



TeamResult teamGetNextIncompleteFormation(Team team, Formation* formation,
        int* index) {
    TeamResult result = TEAM_SUCCESS;
    if (!team || !formation || !index) {
        result = TEAM_NULL_ARGUMENT;
    } else {
        *formation = NULL; /* default result, will be returned if there are no incomplete formations */
        for (int i = 0; i < team->currentFormations; ++i) {
            Formation formationPtr = team->formations[i];
            if (!formationIsComplete(formationPtr)) {
                *formation = formationPtr;
                *index = i;
                break;
            }
        }
    }
    return result;
}

有关如何(特别)避免代码重复的任何建议将不胜感激。

感谢您的时间! :)

6 个答案:

答案 0 :(得分:2)

将空值传递给这些函数似乎是一个编码错误。有三种主要方法可以解决这种情况。

  1. 处理错误的空值并返回错误值。这引入了额外的代码,用于检查返回错误值的参数,以及每个调用站点周围的额外代码,现在必须处理错误返回值。可能没有对这些代码进行测试,因为如果您知道代码错误地传递了空值,那么您只需要修复它。
  2. 使用assert来检查参数的有效性,从而产生干净的错误消息,清除读取前置条件,但需要一些额外的代码。
  3. 没有前提条件检查,并在您推迟NULL时调试段错误。
  4. 根据我的经验3通常是最好的方法。它增加了零额外代码,并且段错误通常与您从2获得的干净错误消息一样容易调试。但是,您会发现许多软件工程师更喜欢2,这是一个品味问题。 / p>

    您的代码(模式1)有一些重大缺点。首先,它添加了无法优化的额外代码。其次,更多代码意味着更复杂。第三,不清楚这些函数是否应该能够接受破坏的参数,或者代码是否只是为了帮助调试错误。

答案 1 :(得分:1)

如果您担心在每个函数的开头重复NULL检查,我不会。它使用户清楚您在进行任何工作之前只是在进行输入验证。无需担心几行。

一般来说,不要为这样的小东西出汗。

答案 2 :(得分:1)

我会创建一个检查团队对象的函数:

TeamResult TeamPtrCheck(Team *team)
{
    if (team == NULL)
        return TEAM_NULL_ARGUMENT;
    else if (teamEmpty(team))
        return TEAM_IS_EMPTY;
    else
        return TEAM_SUCCESS;
}

然后在每个函数的顶部引用+你的其他检查,例如

TeamResult = TeamPtrCheck(team);
if (TeamResult != TEAM_SUCCESS)
    return TeamResult;
if (winRate == NULL)
    return TEAM_NULL_ARGUMENT;

否则,如果每个函数 不同,则将支票保留为不同!

答案 3 :(得分:1)

有一些技术可以减少您所需要的冗余,其中一种适用方式很大程度上取决于您检查的条件的性质。在任何情况下,我都会建议不要使用任何(预处理器)技巧来减少重复,这会隐藏实际发生的事情。

如果您的条件不应该发生,检查它的一种简洁方法是使用断言。有一个断言你基本上说:这个条件必须是真的,否则我的代码有一个bug,请检查我的假设是否正确,如果不是,立即杀死我的程序。这通常是这样使用的:

#include <assert.h>

void foo(int a, int b) {
    assert((a < b) && "some error message that should appear when the assert fails (a failing assert prints its argument)");
    //do some sensible stuff assuming a is really smaller than b
}

一个特例是指针是否为空的问题。做点什么

void foo(int* bar) {
    assert(bar);
    *bar = 3;
}

是没有意义的,因为取消引用空指针会在任何理智的平台上安全地对程序进行分段,因此以下内容将安全地停止您的程序:

void foo(int* bar) {
    *bar = 3;
}

语言律师可能对我所说的不满意,因为根据标准,解除引用空指针是未定义的行为,从技术上讲,编译器将被允许生成格式化硬盘的代码。但是,取消引用空指针是一个常见错误,您可以期望编译器不要使用它做愚蠢的事情,并且您可以期望系统特别注意确保硬件在您尝试执行时会尖叫。这个硬件检查是免费的,断言需要几个周期来检查。

然而,assert(和segfaulting null指针)仅适用于检查致命条件。如果你只是检查一个条件,使一个函数内的任何进一步工作毫无意义,我会毫不犹豫地使用早期返回。它通常更具可读性,特别是因为语法突出显示容易向读者显示返回语句:

void foo(int a, int b) {
    if(a >= b) return;
    //do something sensible assuming a < b
}

使用这个范例,你的第一个函数看起来像这样:

TeamResult teamIsDuplicateCoachName(Team team, bool* isDuplicate) {
    if(!team || !isDuplicate) return TEAM_NULL_ARGUMENT;
    if(teamEmpty(team)) return TEAM_IS_EMPTY;

    for (int i = 0; i < team->currentFormations; ++i) {
        if (teamIsPlayerInFormation(team->formations[i], team->coach)) {
            *isDuplicate = true;
            break;
        }
    }
    return TEAM_SUCCESS;
}

我相信,这比身体周围的版本更清晰简洁。

答案 4 :(得分:0)

这或多或少是一个设计问题。如果上面的函数都是静态函数(或者只有一个是extern函数),那么整个“函数包”应该检查条件 - 逐行执行 - 对每个对象执行一次,并让低级函数的实现细节假设输入数据有效。

例如,如果你回到创建team的任何地方,分配和初始化以及创建formation的任何地方,分配和初始化并构建规则那里确保每个创建的团队都存在并且不存在重复,您将不必有效输入,因为通过定义/构造它将始终有效。这是前提条件的例子。不变量将是这些定义的真实性的持久性(没有函数可能在返回时改变不变状态)并且后置条件将有些相反(例如,当它们是free时,但指针仍然存在于某处)。 / p>

话虽如此,在C中操纵“类似对象”的数据,我个人的偏好是创建创建,返回和销毁这些对象的外部函数。如果成员在具有最小.h接口的.c文件中保持静态,那么您将获得与面向对象编程类似的概念(尽管您永远不能使成员完全“私有”)。

答案 5 :(得分:0)

感谢所有回复者。我在我的原始帖子中提到我不允许更改这些函数的任何规范,因此使用断言和/或允许取消引用NULL的解决方案是不可能的,尽管我我会把它们考虑在其他场合。

考虑到这一点,我认为它是使用函数指针,或者只是保留复制。为了清楚起见,这次我想避免使用函数指针。