什么是意大利面条代码? [关闭]
Posted
技术标签:
【中文标题】什么是意大利面条代码? [关闭]【英文标题】:What is spaghetti code? [closed] 【发布时间】:2010-09-16 18:26:00 【问题描述】:您能否发布一个真实的、过度的意大利面条代码的简短示例,并可能说明它的作用?你能告诉我一个小调试器的噩梦吗?
我不是说IOCCC 代码,那是科幻小说。我的意思是发生在你身上的真实例子......
更新
焦点已从“发布一些意大利面条代码”变为“究竟是什么意大利面条代码?”。从历史的角度来看,目前的选择似乎是:
大量使用计算 goto 的旧 Fortran 代码 使用 ALTER 语句的旧 Cobol 代码【问题讨论】:
我明白了你的问题:意大利面条代码并不真正存在,它是一个虚构的术语,没有真实世界的例子。好点子。 现在你可以编写意大利面条式代码,混合巨大的 UI 类(> 2000 行),其中大多数方法在许多地方被调用,带有线程和大量的 try-catch。 我认为发布意大利面条代码的“示例”并不容易。直到您有 200 个表单、500 个文件和 25 万行代码,其中充满循环交叉引用、全局变量和数千个不带参数且没有返回值的过程,情况的真正严重性才会真正弄清楚.当给你这样的代码并分配重构单个函数的任务时,问题就变得更加清楚,只是发现你还需要重构 732 个其他函数才能不破坏任何东西。 我投票结束这个问题,因为它不属于这里 【参考方案1】:对我来说,一个更现代的意大利面条代码示例是当您有 20 个 dll 并且每个 DLL 以一种或另一种方式相互引用时。你的依赖图看起来像一个巨大的blob,你的代码到处乱跳,没有真正的顺序。一切都是相互依赖的。
【讨论】:
是的。 Goto 并不是制作您永远无法确定是正确的东西的唯一方法。我喜欢“通心粉代码”这个词。【参考方案2】:我不会把这个从我的脑海中拉出来。这就是我必须使用的,尽管是简化的。假设基本上你有一个需要枚举的程序:
enum
a, b, c;
myenum;
但我们所拥有的是
HashTable t;
t["a"] = 0;
t["b"] = 1;
t["c"] = 2;
当然,没有一个哈希表的实现是足够好的,所以有一个哈希表的本地实现,它包含的代码是平均开源实现的大约 10 倍,具有一半的功能和双倍的错误数量。 HashTable 实际上是定义为虚拟的,并且有一个工厂 HashTableFactory 来创建 HashTables 的实例,但是对于模式 HashTableFactory 也是虚拟的。为了防止虚拟类的无限级联,有一个函数
HashTableFactory *makeHashTableFactor();
因此,在代码需要myenum 的任何地方,它都会引用HashTable 和HashTableFactory 的实例,以防您想要制作更多的HashTable。但是等等,这还不是全部!这不是哈希表的初始化方式,而是通过编写读取 XML 的代码来完成的:
<enum>
<item name="a" value="0"/>
<item name="b" value="1"/>
<item name="c" value="2"/>
</enum>
并插入到哈希表中。但是代码是“优化的”,因此它不会读取 ascii 文件 myenum.xml,而是有一个编译时脚本生成:
const char* myenumXML = [13, 32, 53 ....];
来自 myenum.xml,哈希表由函数初始化:
void xmlToHashTable(char *xml, HashTable *h, HashTableFactory *f);
被称为:
HashTableFactory *factory = makeHashTableFactory();
HashTable *t = facotry.make();
xmlToHashTable(myenumXML, t, f);
好的,所以我们有很多代码来获取枚举结构。它基本上用在一个函数中:
void printStuff(int c)
switch (c)
case a: print("a");
case b: print("b");
case c: print("c");
这是在以下上下文中调用的:
void stuff(char* str)
int c = charToEnum(str);
printStuff(c);
所以我们实际上拥有的是而不是
void stuff(char *str)
printf(str);
我们已经设法生成了数千行代码(私有的新代码、错误代码、复杂代码、哈希表的实现以及 xml 读取器和写入器)来代替上述 3。
【讨论】:
这是真的吗?我敢打赌,“某人”刚刚读过《设计模式》这本书,然后想:“哇,我现在可以优化我们所有的东西了!”……这是我见过的“为技术而技术”的最好例子! 【参考方案3】:还有Ravioli Code,正好相反。漂亮的小块功能,干净的界面整齐地包裹着丰富的优点,所有这些都坐落在漂亮的框架中。
【讨论】:
【参考方案4】:来自 Linux SCSI 驱动程序(应保持匿名以保护有罪者):
wait_nomsg:
if ((inb(tmport) & 0x04) != 0)
goto wait_nomsg;
outb(1, 0x80);
udelay(100);
for (n = 0; n < 0x30000; n++)
if ((inb(tmport) & 0x80) != 0) /* bsy ? */
goto wait_io;
goto TCM_SYNC;
wait_io:
for (n = 0; n < 0x30000; n++)
if ((inb(tmport) & 0x81) == 0x0081)
goto wait_io1;
goto TCM_SYNC;
wait_io1:
inb(0x80);
val |= 0x8003; /* io,cd,db7 */
outw(val, tmport);
inb(0x80);
val &= 0x00bf; /* no sel */
outw(val, tmport);
outb(2, 0x80);
TCM_SYNC:
/* ... */
small_id:
m = 1;
m <<= k;
if ((m & assignid_map) == 0)
goto G2Q_QUIN;
if (k > 0)
k--;
goto small_id;
G2Q5: /* srch from max acceptable ID# */
k = i; /* max acceptable ID# */
G2Q_LP:
m = 1;
m <<= k;
if ((m & assignid_map) == 0)
goto G2Q_QUIN;
if (k > 0)
k--;
goto G2Q_LP;
G2Q_QUIN: /* k=binID#, */
我是如何找到这颗宝石的?
find /usr/src/linux -type f -name \*.c |
while read f
do
echo -n "$f "
sed -n 's/^.*goto *\([^;]*\);.*/\1/p' $f | sort -u | wc -l
done |
sort +1rn |
head
输出是一系列文件,列出了按不同标签的 goto 数量排序的文件,如下所示:
kernel/fork.c 31
fs/namei.c 35
drivers/infiniband/hw/mthca/mthca_main.c 36
fs/cifs/cifssmb.c 45
fs/ntfs/super.c 47
【讨论】:
上面有很多 goto,但实际上它们在做什么非常清楚,所以这并没有得到我对意大利面条的投票。 非常清晰?转到 G2Q_QUIN?转到 G2Q_LP? 我确信代码可以以结构化、更易读的风格编写,而不会产生任何重大开销。 .. 因为这是内核模式驱动程序,所以性能至关重要!意大利面条代码更受 CPU 限制,在这些情况下性能更高。 GOTO 仅解析为一个 ASM JMP/LJMP。所以我对这段代码完全没问题。 是的 - 使用while
语句无法有效地完成那些繁忙的等待循环。 (这对无法分辨的人来说是讽刺)。【参考方案5】:
真正的意大利面条代码是在 COBOL 中完成的,并且使用了 ALTER 语句。
这是example,虽然列出了“幽默”,但我见过这种事情。几乎因为注意到任何带有 Alter 语句的程序显然处于犯罪状态而被解雇。我拒绝“维护”那个程序,替换它比理解它更快。
【讨论】:
目前看来这更现实...【参考方案6】:别忘了提到面向对象的意大利面条。 这是你尝试使用书中所有设计模式的时候,即使它们没有意义。这会导致概念级别的意大利面条代码,这比基于 goto 的经典意大利面条代码更不利于质量。
【讨论】:
没错!我见过这么多毫无意义的访客和工厂(但看起来很酷且非常专业)......【参考方案7】:你要求的,你会得到的:
这是播放蓝色多瑙河华尔兹的 DOS .com 文件的来源。可执行文件的大小仅为 176 字节。代码被重新用作数据,反之亦然。
.286
.model tiny
g4 equ 55-48 ; removed note-decoding !
a4 equ 57-48 ; now: storing midi-notes for octaves 0..2 and convert
h4 equ 59-48 ; to 4..6 with a simple add 48.
c5 equ 60-48
d5 equ 62-48
e5 equ 64-48
g5 equ 67-48
h5 equ 71-48
c6 equ 72-48
d6 equ 74-48
e6 equ 76-48
g6 equ 79-48 ; = 00011111b
pp equ 0 ; c4 is not used in the walz, using it as play-pause.
EOM equ 1 ; c#4 is also available... End Of Music
; warning: experts only beyond this point !
pau1 equ 00100000b ; bitfield definitions for note-compression
pau2 equ 01000000b ; you can or a pau to each note!
pau3 equ 01100000b
;rep1 equ 01000000b ; rep1 is history (only used once).
;rep3 equ 11000000b ; rep3 was never used.
rep2 equ 10000000b ; or a rep2 to a note to play it 3 times.
drumsize equ 5
.code
org 100h
start:
mov ah,9
mov dx,offset msg
int 21h ; print our headerstring
mov dx,0330h ; gus midi megaem -port
mov si,offset music_code ; start of music data
mainloop:
; get new note (melody)
xor bp,bp ; bp= repeat-counter
lodsb ; get a new note
cmp al, EOM ; check for end
jne continue
ret
continue:
jns no_rep2 ; check for rep2-Bit
inc bp
inc bp ; "build" repeat-counter
no_rep2:
push ax ; save the note for pause
; "convert" to midi-note
and al,00011111b
jz skip_pp ; check pp, keep it 0
add al,48 ; fix-up oktave
skip_pp:
xchg ax,bx ; bl= midi-note
play_again:
mov cl,3
push cx ; patch program (3= piano)
push 0c8h ; program change, channel 9
; wait (cx:dx) times
mov ah,86h ; wait a little bit
int 15h
; prepare drums
dec di ; get the current drum
jns no_drum_underflow
mov di,drumsize
no_drum_underflow:
; play drum
push dx ; volume drum
push [word ptr drumtrk+di] ; note drum
mov al,99h
push ax ; play channel 10
; play melody
push dx ; volume melody
push bx ; note melody
dec ax ; replaces dec al :)
push ax ; play channel 9
; send data to midi-port
mov cl,8 ; we have to send 8 bytes
play_loop:
pop ax ; get the midi event
out dx,al ; and send it
loop play_loop
; repeat "bp" times
dec bp ; repeat the note
jns play_again
; check and "play" pause
xor bx,bx ; clear the note, so we can hear
; a pause
; decode pause value
pop ax
test al,01100000b
jz mainloop ; no pause, get next note
; decrement pause value and save on stack
sub al,20h
push ax
jmp play_again ; and play next drum
; don't change the order of the following data, it is heavily crosslinked !
music_code db pp or rep2
db g4 or rep2 or pau1
db h4 or pau1, d5 or pau1, d5 or pau3
db d6 or pau1, d6 or pau3, h5 or pau1, h5 or pau3
db g4 or rep2 or pau1
db h4 or pau1, d5 or pau1, d5 or pau3
db d6 or pau1, d6 or pau3, c6 or pau1, c6 or pau3
db a4 or rep2 or pau1
db c5 or pau1, e5 or pau1, e5 or pau3
db e6 or pau1, e6 or pau3, c6 or pau1, c6 or pau3
db a4 or rep2 or pau1
db c5 or pau1, e5 or pau1, e5 or pau3
db e6 or pau1, e6 or pau3, h5 or pau1, h5 or pau3
db g4 or rep2 or pau1
db h4 or pau1, g5 or pau1, g5 or pau3
db g6 or pau1, g6 or pau3, d6 or pau1, d6 or pau3
db g4 or rep2 or pau1
db h4 or pau1, g5 or pau1, g5 or pau3
db g6 or pau1, g6 or pau3, e6 or pau1, e6 or pau3
db a4 or rep2 or pau1
db c5 or pau1, e5 or pau1, e5 or pau3, pp or pau3
db c5 or pau1, e5 or pau1, h5 or pau3, pp or pau3, d5 or pau1
db h4 or pau1, h4 or pau3
db a4 or pau1, e5 or pau3
db d5 or pau1, g4 or pau2
; db g4 or rep1 or pau1
; replace this last "rep1"-note with two (equal-sounding) notes
db g4
db g4 or pau1
msg db EOM, 'Docking Station',10,'doj&sub'
drumtrk db 36, 42, 38, 42, 38, 59 ; reversed order to save some bytes !
end start
【讨论】:
这个不算,汇编语言依赖跳转——你基本上写不出非意大利面的汇编。 代码被复用为数据,你确定你的名字不是梅尔? 作为数据重用的代码也不计算在内。这确实很巧妙,但它不是意大利面条代码。意大利面条式代码是几乎无法遵循控制流程的代码。 Ehh... 尝试跟随 play_loop 的循环。该循环执行 8 次并从堆栈中弹出数据。现在最大的问题是:弹出的 8 个单词从何而来?【参考方案8】:真正的意大利面条代码需要大量非本地 goto。遗憾的是,使用大多数现代语言这是不可能的。
编辑:有些人建议使用例外和 longjmp 作为 GOTO 的替代品。但是这些远是有限和结构化的,因为它们只允许您返回调用堆栈。 Real GOTO 允许您在程序中跳转到 any 行anywhere,这是创建真正的意大利面所必需的。
【讨论】:
你的意思是“悲伤”吗? 使用异常模拟非本地 goto 怎么样? longjmp 和 setjmp 在 C 和 C++ 中运行良好 :-) 很遗憾,我们不能再写晦涩难懂的东西了。那是我小时候真正的工作保障。无法将支持外包给难以理解的东西。 Futurama 语录:“你有多余的 goto 10 吗?” 太有限了?使用延续作为第一类对象,传递它们并将它们存储在深度递归嵌套的结构中可以让你走得很远......(开个玩笑)【参考方案9】:简单来说,意大利面条代码是任何编程语言中的任何代码,其中不可能跟踪下一个执行帖子,或者至少难以确定下一个点在响应一个动作时的位置。
【讨论】:
【参考方案10】:这是我前段时间写的一个 MIDI 解析器。这是一个快速而肮脏的概念证明,但是,我要为它的丑陋负责:4 级嵌套条件加上可怕的多重返回。此代码旨在比较 2 个 MIDI 事件,以便在写入文件时按优先级对它们进行排序。尽管它很丑,但它的工作做得还不错。
internal class EventContainerComparer : IComparer
int IComparer.Compare(object a, object b)
MIDIEventContainer evt1 = (MIDIEventContainer) a;
MIDIEventContainer evt2 = (MIDIEventContainer) b;
ChannelEvent chanEvt1;
ChannelEvent chanEvt2;
if (evt1.AbsoluteTime < evt2.AbsoluteTime)
return -1;
else if (evt1.AbsoluteTime > evt2.AbsoluteTime)
return 1;
else
// a iguar valor de AbsoluteTime, los channelEvent tienen prioridad
if(evt1.MidiEvent is ChannelEvent && evt2.MidiEvent is MetaEvent)
return -1;
else if(evt1.MidiEvent is MetaEvent && evt2.MidiEvent is ChannelEvent)
return 1;
// si ambos son channelEvent, dar prioridad a NoteOn == 0 sobre NoteOn > 0
else if(evt1.MidiEvent is ChannelEvent && evt2.MidiEvent is ChannelEvent)
chanEvt1 = (ChannelEvent) evt1.MidiEvent;
chanEvt2 = (ChannelEvent) evt2.MidiEvent;
// si ambos son NoteOn
if( chanEvt1.EventType == ChannelEventType.NoteOn
&& chanEvt2.EventType == ChannelEventType.NoteOn)
// chanEvt1 en NoteOn(0) y el 2 es NoteOn(>0)
if(chanEvt1.Arg1 == 0 && chanEvt2.Arg1 > 0)
return -1;
// chanEvt1 en NoteOn(0) y el 2 es NoteOn(>0)
else if(chanEvt2.Arg1 == 0 && chanEvt1.Arg1 > 0)
return 1;
else
return 0;
// son 2 ChannelEvent, pero no son los 2 NoteOn, el orden es indistinto
else
return 0;
// son 2 MetaEvent, el orden es indistinto
else
return 0;
【讨论】:
很好的例子:你不需要 goto 来获得那种老式的意大利面条外观!【参考方案11】:这是Duff's Device,来自马特对this question的回答:
int n = (count + 7) / 8;
switch (count % 8)
case 0: do *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
while (--n > 0);
【讨论】:
Duff 的设备虽然不是意大利面条... ;) 你还是得到了我的 +1... :)【参考方案12】:意大利面条代码:意大利面条代码起源于 60 年代初,作为某些意大利面食的替代食谱,它是由一位试图自动化创建万无一失的主菜的餐厅企业家制作的。由于缺乏时间来完成设计,工程师/厨师偷工减料,这在早期的配方中引入了问题。为了补救一个坏掉的好主意,随着配方失控,各种香料迅速添加到混合物中。结果是一堆冗长、曲折但可能很美味的文本,后来成为全世界开发人员所珍视的做法。
【讨论】:
我与开发人员所珍视的潜在美味堆在一起。不确定我是否同意美味或珍惜。不过,其他一切都是真实的。【参考方案13】:您是否看过 Flex/Bison 扫描仪和生成器生成的代码?大量的标签和预处理指令。
绝对不可能看懂里面的内容..也绝对不可能跟上程序的流程。
这绝对是意大利面条代码。
【讨论】:
以上是关于什么是意大利面条代码? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章