TCL:从嵌套的 if 语句中使代码更清晰

TCL: Make code cleaner from nested if-statements

这是 i3 window 管理器 linux 上状态栏项目之一的程序。每秒是运行。基本上涉及频率调节器。如果温度达到一定数值,则切换到省电模式,或者如果某个应用程序正在 运行ning,例如steam,或者笔记本电脑 运行 正在使用电池。如果温度达到较低点,那么它会切换到性能等。

程序 运行 目前为止非常好,没有问题。然而,代码有太多嵌套的 if-else 语句,很难维护,每次我添加一些东西时,代码都会变得更多,好吧......嵌套。

proc cpu_freq {} {
    set app steam
    set cpu_power [exec sudo cpupower frequency-info | sed -ne /speed/p]
    set cpu_temp [exec sensors | grep Core | sed -n  {2p} | awk {{print }} | cut -c2-3]
    set battery [exec acpi]
    if {[string match *performance* $cpu_power]} {set cpu_freq HIGH; set color "$::green"}
    if {[string match *powersave* $cpu_power]}   {set cpu_freq LOW;  set color "$::red"}
    if {![file isfile $::i3dir/powersave.freq] && ![file isfile $::i3dir/performance.freq]} {
        set switch AUTO
    }
        # ON BATTERY 
    if {[string match *Discharging* $battery]} {
        # WHEN IN PERFORMANCE MODE
        if {[string match *performance* $cpu_power]} {
           if {![file isfile $::i3dir/performance.freq]} {
               # AND NOT IN MANUAL
               # SWITCH TO POWERSAVE 
               exec sudo cpupower frequency-set -g powersave
               set cpu_freq LOW
               set switch AUTO
               set color "$::red"
               set ::on_battery true
          } else { 
              # SWITCH TO MANUAL PERFORMANCE MODE
              if {[file isfile $::i3dir/performance.freq]} {
                  exec sudo cpupower frequency-set -g performance
                  set cpu_freq HIGH
                  set switch MAN
                  set color "$::green"
                  set ::on_battery true
              } else {
             if {[file isfile $::i3dir/powersave.freq]} {
                 # SWITCH TO MANUAL POWERSAVE MODE 
                 exec sudo cpupower frequency-set -g powersave
                 set cpu_freq LOW
                 set switch MAN
                 set color "$::red"
                 set ::on_battery true
              }
              }                
           } 
       } else {
       # WHEN IN POWERSAVE MODE (AUTO)
       # SWITCH TO MANUAL POWERSAVE
       if {[string match *powersave* $cpu_power]} {
          if {[file isfile $::i3dir/powersave.freq]} {
              exec sudo cpupower frequency-set -g powersave
              set cpu_freq LOW
              set switch MAN
              set color "$::red"
              set ::on_battery true
          } else {
       # SWITCH TO MANUAL PERFORMANCE
          if {[file isfile $::i3dir/performance.freq]} {
              exec sudo cpupower frequency-set -g performance
              set cpu_freq HIGH
              set switch MAN
              set color "$::green"
              set ::on_battery true
             }
          }
       }
    }
       # ON MAINS
    } else {
         # WHEN IN POWERSAVE MODE
         if {[string match *powersave* $cpu_power]} {
                # RUNNING APP OR MANUAL SWITCH
            if {[file isfile $::i3dir/powersave.freq]} {
                set cpu_freq LOW
                set switch MAN
                } else {
            if {[isRunning $app]} {
                set cpu_freq LOW
                set switch AUTO
                # DO NOTHING, KEEP RUNNING IN POWERSAVE MODE
                } else {
                # SWITCH TO PERFORMANCE AFTER RUNNING ON BATTERIES
                if {$::on_battery==true} {
                    exec sudo cpupower frequency-set -g performance
                    set cpu_freq HIGH
                    set switch AUTO
                    set color "$::green"
                    set ::on_battery false
                # SWITCH TO PERFORMANCE WHEN REACHING LOWER TEMPS
                } elseif {$cpu_temp <= 55} {
                    exec sudo cpupower frequency-set -g performance
                    set cpu_freq HIGH
                    set switch AUTO
                    set color "$::green"
                   }
                }
            }
         # WHEN IN PERFORMANCE MODE
        } else {
                # MANUAL SWITCH
            if {[file isfile $::i3dir/performance.freq]} {
                set switch MAN
                set cpu_freq HIGH
                # DO NOTHING, KEEP RUNNING IN PERFORMANCE MODE
                } else {
                # HOT TEMPERATURE OR RUNNING APP
                # SWITCH TO POWERSAVE
                if {$cpu_temp >= 75 || [isRunning $app] } {
                    exec sudo cpupower frequency-set -g powersave
                    set cpu_freq LOW
                    set switch AUTO
                    set color "$::red"
                } else {
                    set cpu_freq HIGH
                    set switch AUTO
                }
            }
        } 
    }
    set stdout {{"name":"cpu_freq","full_text":"$switch:$cpu_freq","color":"$color"}}
    set stdout [subst -nocommands $stdout]
    puts -nonewline $stdout
}

将其分解为一组函数。

Tcl 有一个 switch 语句,有时可以提供帮助。它还具有 elseif 以帮助减少嵌套。但在显示的代码中,将其分解为具有合理名称的函数,您可以将其简化为一个处理逻辑的函数和一个处理特定情况下发生的事情的集合。

按照 patthoyts 的建议将代码分解为单独的函数是一个很好的解决方案,但可能会稍微慢一些(但是您不太可能会注意到)。另一种使代码更易于使用的解决方案是在启动期间动态创建 cpu_freq

为此,请编写一个尽可能冗长且包含尽可能多的文档的脚本,从而生成您想要 cpu_freq 的简洁高效的正文。当您需要扩展它时,只需向脚本中添加更多部分即可。将生成的主体作为第三个参数调用 proc,它将在第一次调用时编译。

当我看到这样的东西时,我会立即想到有限状态 machines/state 转换图。你有一个起始状态,然后根据你在 if 语句中调用的过程的结果切换到其他状态,在某个时候你到达一个结束状态,从那里不能进一步转换。

所以我会考虑重组为如下示例:

# The value to process
set value "This is a big red ball"

# The starting state
set state 1

# The state transtions and the functions to implement them
set states [dict create "1,3" "IsRed" "1,2" "IsBlue" "2,4" "IsBig" "2,5" "IsSmall" "3,4" "IsBig" "3,5" "IsSmall"]

# Procs that implement the state transitions
proc IsRed {next} {
    global value state
    if {[string first "red" $value] != -1} {
        puts "red"
        set state $next
        return true
    }
    return false
}

proc IsBlue {next} {
    global value state
    if {[string first "blue" $value] != -1} {
        puts "blue"
        set state $next
        return true
    }
    return false
}
proc IsSmall {next} {
    global value state
    if {[string first "small" $value] != -1} {
        puts "small"
        set state $next
        return true
    }
    return false
}

proc IsBig {next} {
    global value state
    if {[string first "big" $value] != -1} {
        puts "big"
        set state $next
        return true
    }
    return false
}

# Proc to run the state machine until the state stops changing
proc runMachine { states } {
    global state
    set startState -1
    while { $state != $startState } {
        set startState $state
        foreach key [dict keys $states "$state,*"] {
            set next [lindex [split $key ","] 1]
            set res [[dict get $states $key] $next]
            # If the state changes then no need to do any more processing
            if { $res == true } {
               break 
            }
        }
    }
}

runMachine $states

这是一种可能的方法,它比您需要做的要简单得多,但展示了基本思想。字典显示了允许的状态转换和到 运行 的过程,以测试是否允许转换。我已经将我的处理代码(puts 语句)放在这个函数中,但是让另一个函数进行处理会很简单,直接调用或作为字典中的另一个值保存并从 运行Machine proc 调用.

set states [dict create 21,3" [list "IsRed" "RedAction"]]

这种方法可以让您分离出所有的动作和转换,并绘制一个状态转换图,清楚地显示正在发生的事情。

TCL 有限状态机的快速 google 显示了实现此想法的许多其他方法。