我正在使用C语言编写的遗留系统上工作。我正在将代码从几个大型模块重构为几个较小的,逻辑上独立的共享库。

问题在于,现有的代码使这种划分变得困难,因为大型模块试图做太多事情。其他其他挑战是:


现有代码非常紧密地耦合在一起:

例如,应该实现集合结构(实际上是动态数组)的模块还具有例程,该例程从文件,数据库中检索数据(以填充结构),并读取缓存的数据等。
该代码大量使用了全局变量,当我将代码划分到单独的共享库中时,我不是怎么工作的(如果?)。


现有的标头如下所示:

/* DYN_ARRAY header */
DYN_ARRAY* DYN_ARRAY_Alloc();
void       DYN_ARRAY_Free(DYN_ARRAY *ptr);
int        DYN_ARRAY_LoadFile(DYN_ARRAY *ptr, cont char* filename, FILE_STRUCT_INFO *info);

/* obvious dependency on database functionality */
int        DYN_ARRAY_LoadQueryResults(DYN_ARRAY *ptr, const char* sql);

/* This innocuous looking function calls a function which introduces
   a dependencies on another logically separate module


* /
    int DYN_ARRAY_GetIdKeyValue(const DYN_ARRAY * ptr,const int key_id);复制代码

我正在考虑将现有的DYN_ARRAY模块分为三个共享库,如下所示:


dyn_array_core(取决于:无)
dyn_array_db(取决于:dyn_array_core,db_utils ...)
dyn_array_misc(取决于:dyn_array_core,misc_utils ...)


我的问题是:


这是一种明智的方法(还是有更好的方法来划分代码?)
分区代码是否会像以前一样工作(给出代码使用全局变量的事实,即每个dll是否都有自己的全局var副本?[如果是,那么显然那不是我想要的] –在这种情况下,我该如何重构代码以像以前一样工作?)

最佳答案

如果您要进行更改以完成某些有用的操作,那么将代码移到单独的共享库中并不会做很多事情。在我看来,一种更好的方法是首先重写代码以消除尽可能多的全局变量-而是建立一个或多个结构来保存上下文,并修改函数签名以包括上下文指针。到了那里之后,将代码分成更好的描述模块(也许还有多个共享库)将为您提供更有用的输出。

首先,您可能需要创建一个新的标头,其目的是定义以前的全局变量重定位到的结构。首先请注意,我们要进行所有这些重构而不要将代码移到共享库中-仅在您确认初始重构成功后才进行。 (即,不要一次引入过多的潜在故障点。)

#ifndef _FORMER_GLOBALS_H_
#define _FORMER_GLOBALS_H_

typedef struct GlobalContext {
} GlobalContext;

GlobalContext *CreateGlobalContext(); // a convenience function

#endif /* _FORMER_GLOBALS_H_ */


下一步是将要消除的所有全局变量移入此结构。如果您知道所有全局变量的定义位置,那么任务就很容易...只需将它们切出其源位置,注意任何非零初始值,然后将变量定义移至结构中(无需初始化)。如果全局变量的当前位置定义不正确(它们散布在各处),则此步骤会更加困难,但是您的编译器工具可能能够帮助您在当前代码中找到全局变量。

接下来考虑CreateGlobalContext()函数。该函数将分配一个上下文结构并对其进行初始化。如果没有非零初始化,则可以完全消除该函数。

GlobalContext *CreateGlobalContext()
{
    GlobalContext *context = malloc(sizeof(*context));
    memset(context, 0, sizeof(*context)); // initialize it to all zeros

    // if needed, initialize individual non-zero elements
    // context->some_non_zero = 1;

    return context;
}


既然您没有更多的全局变量了(在您计划移动的代码模块中),并且可以通过对其进行适当的初始化为其创建存储的方法,那么当前您将获得一堆无法编译的代码-您应该尚未解决所有先前全局变量的引用或未知标识符。

访问已移动的全局变量的任何函数都应添加一个新参数。因此,例如,考虑这种变化:

extern int global_a;
void FunctionToBeMoved(int a)
{
    global_a = a;
}

// the above old function becomes:
void FunctionToBeMoved(GlobalContext *context, int a)
{
    context->global_a = a;
}


完成这些转换后,您将需要修改每个已更改函数的调用方式-它们需要简单地传递它们现在正在接收的上下文(或者如果由于以下原因而将其遗漏则需要接收)缺乏直接的全球用途)。

这不是一件小事,但是将糟糕的代码转换为通常可读/可维护的代码是很大的。

10-07 19:31
查看更多