然而,作为安全关键的改变是昂贵的。因此,不想解决“不会破产”的问题,我对其他论点持开放态度。
最关键的代码得到的关注最少,这是自相矛盾的死亡螺旋,因为人们害怕改变它。
你犹豫要不要对这段代码进行简单的、死记硬背的重构,这告诉我代码要么没有测试,要么你不信任这些测试。当你害怕改进代码因为你可能会破坏它时,就会延迟对代码的改进。您可能会做尽可能小的事情,这会使代码更加脆弱和不安全。
我建议第一件事是进行一些测试以及用于试验的暂存环境。然后所有更改都变得更安全。当你发现这段代码正在做的所有奇怪和危险的事情时,最初可能会有一些失态,但这就是暂存区的用途。从中长期来看,每个人都会更快、更有信心地改进此代码。使代码更容易和更安全地更改可以使其更容易和更安全地更改;然后螺旋上升,而不是下降。
使宏看起来像单个变量的技术是我以前在 Perl 5 代码库中看到的技术。它是用 C 宏而不是 C 编写的。例如,这里有一些操作 Perl 调用堆栈。
#define SP sp
#define MARK mark
#define TARG targ
#define PUSHMARK(p) \
STMT_START { \
I32 * mark_stack_entry; \
if (UNLIKELY((mark_stack_entry = ++PL_markstack_ptr) \
== PL_markstack_max)) \
mark_stack_entry = markstack_grow(); \
*mark_stack_entry = (I32)((p) - PL_stack_base); \
DEBUG_s(DEBUG_v(PerlIO_printf(Perl_debug_log, \
"MARK push %p %" IVdf "\n", \
PL_markstack_ptr, (IV)*mark_stack_entry))); \
} STMT_END
#define TOPMARK S_TOPMARK(aTHX)
#define POPMARK S_POPMARK(aTHX)
#define INCMARK \
STMT_START { \
DEBUG_s(DEBUG_v(PerlIO_printf(Perl_debug_log, \
"MARK inc %p %" IVdf "\n", \
(PL_markstack_ptr+1), (IV)*(PL_markstack_ptr+1)))); \
PL_markstack_ptr++; \
} STMT_END
#define dSP SV **sp = PL_stack_sp
#define djSP dSP
#define dMARK SV **mark = PL_stack_base + POPMARK
#define dORIGMARK const I32 origmark = (I32)(mark - PL_stack_base)
#define ORIGMARK (PL_stack_base + origmark)
#define SPAGAIN sp = PL_stack_sp
#define MSPAGAIN STMT_START { sp = PL_stack_sp; mark = ORIGMARK; } STMT_END
#define GETTARGETSTACKED targ = (PL_op->op_flags & OPf_STACKED ? POPs : PAD_SV(PL_op->op_targ))
#define dTARGETSTACKED SV * GETTARGETSTACKED
这些是宏上的宏。Perl 5 源代码充满了它们。那里发生了很多不透明的魔法。其中一些需要是宏才能允许赋值,但许多可能是内联函数。尽管它们是公共 API的一部分,但部分原因是它们是宏而不是函数,因此它们被无差别地记录在案。
如果您已经非常熟悉 Perl 5 源代码,这种风格非常聪明和有用。对于其他所有人来说,这使得 Perl 5 内部非常难以使用。虽然一些编译器会为宏扩展提供堆栈跟踪,但其他编译器只会报告扩展的宏,让人摸不着头脑,const I32 origmark = (I32)(mark - PL_stack_base)
因为它从未出现在您的源代码中。
与许多宏 hack 一样,虽然该技术非常聪明,但对许多程序员来说也是令人费解且不熟悉的。在安全关键代码中,思维弯曲不是您想要的。你想要简单、无聊的代码。仅此一项就是用命名良好的 getter 和 setter 函数替换它的最简单的参数。相信编译器会优化它们。
这方面的一个很好的例子是 GLib,它仔细地使用了有据可查的类似函数的宏来制作通用数据结构。例如,向数组添加一个值。
#define g_array_append_val(a,v)
虽然这是一个宏,但它的作用和记录就像一个函数。它只是作为一种创建安全类型泛型数组的机制的宏。它不隐藏任何变量。您可以安全地使用它,而无需意识到它是一个宏。
总之,是的,改变它。但不要简单地替换X_myvar
为inst.a.a
考虑创建继续提供封装的函数。
void s1_set_a( s1 *s, int val ) {
s->a.a = val;
}
int s1_get_a( s1 *s ) {
return s->a.a;
}
s1_set_a(&inst, 10);
printf("myvar = %d\n", s1_get_a(&inst) + 1);
的内部s1
是隐藏的,以便以后更容易更改内部(例如,更改s1.a
为指针以节省内存)。您正在使用的变量很清楚,使整个代码更容易理解。函数名称清楚地解释了正在发生的事情。因为它们是函数,所以它们有一个明显的文档位置。相信编译器知道如何最好地优化它。