我正在使用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;
}
完成这些转换后,您将需要修改每个已更改函数的调用方式-它们需要简单地传递它们现在正在接收的上下文(或者如果由于以下原因而将其遗漏则需要接收)缺乏直接的全球用途)。
这不是一件小事,但是将糟糕的代码转换为通常可读/可维护的代码是很大的。