※当サイトの記事には、広告・プロモーションが含まれます。

単一責任の原則(Single Responsibility Principle)に反してるのはOxidizedの問題なのかRubyの問題なのか

www.itmedia.co.jp

 米Microsoftは、復活するスリーマイル島原子力発電所1号機の電力を独占購入する20年契約を、同発電所保有する米Constellation Energyと結んだ。Constellationが9月20日(現地時間)に発表した。

Microsoft、スリーマイル島原発の電力独占購入へ AI需要対策で - ITmedia NEWS

 Microsoftのエネルギー担当副社長、ボビー・ホリス氏はこの契約の発表文で「この契約は、カーボンネガティブを目指すわれわれの取り組みを支える、電力網の脱炭素化に向けた取り組みにおける大きな節目だ」と語った。

Microsoft、スリーマイル島原発の電力独占購入へ AI需要対策で - ITmedia NEWS

⇧ う~む...

「脱炭素化」の取組で「二酸化炭素の排出量の削減」に貢献しようとするのは良いのだが、「放射性廃棄物」の「処理問題」とかには目をつぶる感じなんかね...

まぁ、『何かを得るには、何かを失う』というような言葉もありますが、Microsoft側がやってることは問題を別の問題に転嫁させてるだけではありますな。

「脱炭素化」とか話題に上げずに、AIの活用に膨大な電力が必要な点だけ述べれば良かった気はしますな。

それか、「放射性廃棄物」の「処理問題」についても言及すべきですかね。

公表するのなら、

  • 火力発電における問題
  • 原子力発電における問題

どちらについての取組も明かすべきなんではという気がしますけど...

ちなみに、

www.jera.co.jp

www.wsew.jp

⇧ 日本が魁て「火力発電」で「CO2を出さない」という取組を行っているそうな。

ただ、

いずれも、埋蔵されている地下資源を元に発電しているからして、地下資源が枯渇したら継続できないのであって、人類がこの先どれだけ存続していくかは分かりませんが、「エネルギー問題」が解決できないと、「電力」を生み出せない時代が到来し、科学技術の発展も停滞していきそうではありますな。

まぁ、我々が生きている間は無用な心配とは思いますが...

とりあえず、明るくない未来に向かっていることは間違いないということになりますかね...

ちなみに、日本における「電気」の発祥は、

www.fepc.or.jp

⇧ 1882年と記録されてるらしく、今が、2024年なので、142年が経過してますと。

エネルギーを生み出す地下資源が枯渇する時期は不明ですが、

www.fepc.or.jp

⇧ エネルギーの消費量は、年々、右肩上がりではあるらしい。

いつか「電気」の無い時代が再来する日がやってくるということですかね。

単一責任の原則(Single Responsibility Principle)とは

Wikipediaによると、

単一責任の原則 (たんいつせきにんのげんそく、single-responsibility principle) は、プログラミングに関する原則であり、モジュール、クラスまたは関数は、単一の機能について責任を持ち、その機能をカプセル化するべきであるという原則である。モジュール、クラスまたは関数が提供するサービスは、その責任と一致している必要がある。

単一責任の原則 - Wikipedia

⇧ とありますと。

つまり、役割分担をしっかりしなさいよ、ってことなんだと思われますと。

例えば、

『あるデータを取得して、要件に沿って加工し、情報として保存する』

というような機能であれば、

  1. データを取得する
  2. データを加工し意味ある情報にする
  3. 保存する

の3つの大まかな処理に分けられるので、少なくとも3つのメソッドに分けられますと。

これを、1つのメソッドとかにしてしまうと、「単一責任の原則(Single Responsibility Principle)」に違反することになりますと。

勿論、3つのメソッドに分けた上で、3つのメソッドを利用する1つのメソッドを定義するのはありかと。(所謂、コンポーネント化って呼ばれる手法ですかね)

メソッドを分ける粒度が難しいところではありますが、ソースコードの可読性といった意味でも、分けられないかなと意識することは大事な気がしますと。

「単一責任の原則(Single Responsibility Principle)」は、

SOLID(ソリッド)は、ソフトウェア工学の用語であり、特にオブジェクト指向で用いられる五つの原則の頭字語である。ソフトウェア設計をより平易かつ柔軟にして保守しやすくすることを目的にしている。その特徴はインターフェースを仲介にしての機能の使用と、インターフェースによる機能の注入である。

SOLID - Wikipedia

この五つの原則は、アメリカのソフトウェア技術者ロバート・C・マーティン英語版が提唱していた数々の設計原則の中からチョイスされたものである。マーティンが提唱していた原則は、例えば2000年に発表されたレポート『Design Principles and Design Patterns』の中で紹介されている

SOLID - Wikipedia

そのうち五原則をSOLIDという語呂合わせの頭字語にして普及させたのは、ソフトウェア技術者マイケル・フェザーズであり、2004年以降に広く知られるようになった。SOLIDはオブジェクト指向設計由来であるが、アジャイルソフトウェア開発適応的ソフトウェア開発英語版といった方法論の哲学にもなっている。

SOLID - Wikipedia

⇧「SOLID」の内の1つになりますと。

単一責任の原則(Single Responsibility Principle)に反してるのはOxidizedの問題なのかRubyの問題なのか

で、ネットワーク機器の設定情報をバックアップする「Oxidized」というライブラリがあるのだけど、ちょっと、ソースコードが分かり辛い。

Ruby初学者であるからなのかもしれませんが...

github.com

https://github.com/ytti/oxidized/blob/master/lib/oxidized/model/model.rb

require 'strscan'
require_relative 'outputs'

module Oxidized
  class Model
    using Refinements

    include Oxidized::Config::Vars

    class << self
      def inherited(klass)
        super
        if klass.superclass == Oxidized::Model
          klass.instance_variable_set '@cmd',     (Hash.new { |h, k| h[k] = [] })
          klass.instance_variable_set '@cfg',     (Hash.new { |h, k| h[k] = [] })
          klass.instance_variable_set '@procs',   (Hash.new { |h, k| h[k] = [] })
          klass.instance_variable_set '@expect',  []
          klass.instance_variable_set '@comment', nil
          klass.instance_variable_set '@prompt',  nil
        else # we're subclassing some existing model, take its variables
          instance_variables.each do |var|
            iv = instance_variable_get(var)
            klass.instance_variable_set var, iv.dup
            @cmd[:cmd] = iv[:cmd].dup if var.to_s == "@cmd"
          end
        end
      end

      def comment(str = "# ")
        @comment = if block_given?
                     yield
                   elsif not @comment
                     str
                   else
                     @comment
                   end
      end

      def prompt(regex = nil)
        @prompt = regex || @prompt
      end

      def cfg(*methods, **args, &block)
        [methods].flatten.each do |method|
          process_args_block(@cfg[method.to_s], args, block)
        end
      end

      def cfgs
        @cfg
      end

      def cmd(cmd_arg = nil, **args, &block)
        if cmd_arg.instance_of?(Symbol)
          process_args_block(@cmd[cmd_arg], args, block)
        else
          process_args_block(@cmd[:cmd], args, [cmd_arg, block])
        end
        Oxidized.logger.debug "lib/oxidized/model/model.rb Added #{cmd_arg} to the commands list"
      end

      def cmds
        @cmd
      end

      def expect(regex, **args, &block)
        process_args_block(@expect, args, [regex, block])
      end

      def expects
        @expect
      end

      # calls the block at the end of the model, prepending the output of the
      # block to the output string
      #
      # @author Saku Ytti <saku@ytti.fi>
      # @since 0.0.39
      # @yield expects block which should return [String]
      # @return [void]
      def pre(**args, &block)
        process_args_block(@procs[:pre], args, block)
      end

      # calls the block at the end of the model, adding the output of the block
      # to the output string
      #
      # @author Saku Ytti <saku@ytti.fi>
      # @since 0.0.39
      # @yield expects block which should return [String]
      # @return [void]
      def post(**args, &block)
        process_args_block(@procs[:post], args, block)
      end

      # @author Saku Ytti <saku@ytti.fi>
      # @since 0.0.39
      # @return [Hash] hash proc procs :pre+:post to be prepended/postfixed to output
      attr_reader :procs

      private

      def process_args_block(target, args, block)
        if args[:clear]
          if block.instance_of?(Array)
            target.reject! { |k, _| k == block[0] }
            target.push(block)
          else
            target.replace([block])
          end
        else
          method = args[:prepend] ? :unshift : :push
          target.send(method, block)
        end
      end
    end

    attr_accessor :input, :node

    def cmd(string, &block)
      Oxidized.logger.debug "lib/oxidized/model/model.rb Executing #{string}"
      out = @input.cmd(string)
      return false unless out

      out = out.b unless Oxidized.config.input.utf8_encoded?
      self.class.cmds[:all].each do |all_block|
        out = instance_exec out, string, &all_block
      end
      if vars :remove_secret
        self.class.cmds[:secret].each do |all_block|
          out = instance_exec out, string, &all_block
        end
      end
      out = instance_exec out, &block if block
      process_cmd_output out, string
    end

    def output
      @input.output
    end

    def send(data)
      @input.send data
    end

    def expect(...)
      self.class.expect(...)
    end

    def cfg
      self.class.cfgs
    end

    def prompt
      self.class.prompt
    end

    def expects(data)
      self.class.expects.each do |re, cb|
        if data.match re
          data = cb.arity == 2 ? instance_exec([data, re], &cb) : instance_exec(data, &cb)
        end
      end
      data
    end

    def get
      Oxidized.logger.debug 'lib/oxidized/model/model.rb Collecting commands\' outputs'
      outputs = Outputs.new
      procs = self.class.procs
      self.class.cmds[:cmd].each do |command, block|
        out = cmd command, &block
        return false unless out

        outputs << out
      end
      procs[:pre].each do |pre_proc|
        outputs.unshift process_cmd_output(instance_eval(&pre_proc), '')
      end
      procs[:post].each do |post_proc|
        outputs << process_cmd_output(instance_eval(&post_proc), '')
      end
      outputs
    end

    def comment(str)
      data = ''
      str.each_line do |line|
        data << self.class.comment << line
      end
      data
    end

    def xmlcomment(str)
      # XML Comments start with <!-- and end with -->
      #
      # Because it's illegal for the first or last characters of a comment
      # to be a -, i.e. <!--- or ---> are illegal, and also to improve
      # readability, we add extra spaces after and before the beginning
      # and end of comment markers.
      #
      # Also, XML Comments must not contain --. So we put a space between
      # any double hyphens, by replacing any - that is followed by another -
      # with '- '
      data = ''
      str.each_line do |_line|
        data << '<!-- ' << str.gsub(/-(?=-)/, '- ').chomp << " -->\n"
      end
      data
    end

    def screenscrape
      @input.class.to_s.match(/Telnet/) || vars(:ssh_no_exec)
    end

    private

    def process_cmd_output(output, name)
      output = String.new('') unless output.instance_of?(String)
      output.process_cmd(name)
      output
    end
  end
end    

⇧ 例えば、上記のクラスの灰色に塗りつぶした部分の120行目~136行目のメソッドは、

  1. 引数1(文字列)、引数2(ブロック)を受け取り、引数1(文字列)のコマンドを実行する
  2. 1の処理の結果を、引数2(ブロック)の引数部分に渡して引数2(ブロック)の処理を実行する
  3. 2の処理の結果を、加工する
  4. 戻り値を返す

という感じで、1つのメソッドに処理を盛り込み過ぎな感じがありますと。

この初見殺しっぽいソースコードの構成が、

  1. Ruby特有の言語の仕様のせいなのか
  2. Oxidizedの実装の問題なのか

分からないのですが、明らかに、「単一責任の原則(Single Responsibility Principle)に反して」に反していそうですと。

Rubyの「ブロック」という仕様のせいだと思うのだけど、素直に、メソッドを分解すれば良いような気がするんだが、Rubyだと、1つのメソッドで複数の処理を行うのが一般的なんですかね?

しかも、メソッドを利用している部分を確認したところ、

https://github.com/ytti/oxidized/blob/master/lib/oxidized/model/ios.rb

class IOS < Oxidized::Model
  using Refinements

  prompt /^([\w.@()-]+[#>]\s?)$/
  comment  '! '

  # example how to handle pager
  # expect /^\s--More--\s+.*$/ do |data, re|
  #  send ' '
  #  data.sub re, ''
  # end

  # non-preferred way to handle additional PW prompt
  # expect /^[\w.]+>$/ do |data|
  #  send "enable\n"
  #  send vars(:enable) + "\n"
  #  data
  # end

  cmd :all do |cfg|
    # cfg.gsub! /\cH+\s{8}/, ''         # example how to handle pager
    # cfg.gsub! /\cH+/, ''              # example how to handle pager
    # get rid of errors for commands that don't work on some devices
    cfg.gsub! /^% Invalid input detected at '\^' marker\.$|^\s+\^$/, ''
    cfg.cut_both
  end

  cmd :secret do |cfg|
    cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>'
    cfg.gsub! /^(snmp-server host \S+( vrf \S+)?( informs?)?( version (1|2c|3 (noauth|auth|priv)))?)\s+\S+((\s+\S*)*)\s*/, '\\1 <secret hidden> \\7'
    cfg.gsub! /^(username .+ (password|secret) \d) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(enable (password|secret)( level \d+)? \d) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+(?:password|secret)) (?:\d )?\S+/, '\\1 <secret hidden>'
    cfg.gsub! /^(.*wpa-psk ascii \d) (\S+)/, '\\1 <secret hidden>'
    cfg.gsub! /^(.*key 7) (\d.+)/, '\\1 <secret hidden>'
    cfg.gsub! /^(tacacs-server (.+ )?key) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(crypto isakmp key) (\S+) (.*)/, '\\1 <secret hidden> \\3'
    cfg.gsub! /^(\s+ip ospf message-digest-key \d+ md5) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+ip ospf authentication-key) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+neighbor \S+ password) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+vrrp \d+ authentication text) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+standby \d+ authentication) .{1,8}$/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+standby \d+ authentication md5 key-string) .+?( timeout \d+)?$/, '\\1 <secret hidden> \\2'
    cfg.gsub! /^(\s+key-string) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^((tacacs|radius) server [^\n]+\n(\s+[^\n]+\n)*\s+key) [^\n]+$/m, '\1 <secret hidden>'
    cfg.gsub! /^(\s+ppp (chap|pap) password \d) .+/, '\\1 <secret hidden>'
    cfg
  end

  cmd 'show version' do |cfg|
    comments = []
    comments << cfg.lines.first
    lines = cfg.lines
    lines.each_with_index do |line, i|
      slave = ''
      slaveslot = ''

      if line =~ /^Slave in slot (\d+) is running/
        slave = " Slave:"
        slaveslot = ", slot #{Regexp.last_match(1)}"
      end

      comments << "Image:#{slave} Compiled: #{Regexp.last_match(1)}" if line =~ /^Compiled (.*)$/

      comments << "Image:#{slave} Software: #{Regexp.last_match(1)}, #{Regexp.last_match(2)}" if line =~ /^(?:Cisco )?IOS .* Software,? \(([A-Za-z0-9_-]*)\), .*Version\s+(.*)$/

      comments << "ROM Bootstrap: #{Regexp.last_match(3)}" if line =~ /^ROM: (IOS \S+ )?(System )?Bootstrap.*(Version.*)$/

      comments << "BOOTFLASH: #{Regexp.last_match(1)}" if line =~ /^BOOTFLASH: .*(Version.*)$/

      comments << "Memory: nvram #{Regexp.last_match(1)}" if line =~ /^(\d+[kK]) bytes of (non-volatile|NVRAM)/

      comments << "Memory: flash #{Regexp.last_match(1)}" if line =~ /^(\d+[kK]) bytes of (flash memory|flash internal|processor board System flash|ATA CompactFlash)/i

      comments << "Memory: pcmcia #{Regexp.last_match(2)} #{Regexp.last_match(3)}#{Regexp.last_match(4)} #{Regexp.last_match(1)}" if line =~ /^(\d+[kK]) bytes of (Flash|ATA)?.*PCMCIA .*(slot|disk) ?(\d)/i

      if line =~ /(\S+(?:\sseries)?)\s+(?:\((\S+)\)\s+processor|\(revision[^)]+\)).*\s+with (\S+k) bytes/i
        sproc = Regexp.last_match(1)
        cpu = Regexp.last_match(2)
        mem = Regexp.last_match(3)
        cpuxtra = ''
        comments << "Chassis type:#{slave} #{sproc}"
        comments << "Memory:#{slave} main #{mem}"
        # check the next two lines for more CPU info
        comments << "Processor ID: #{Regexp.last_match(1)}" if cfg.lines[i + 1] =~ /processor board id (\S+)/i
        if cfg.lines[i + 2] =~ /(cpu at |processor: |#{cpu} processor,)/i
          # change implementation to impl and prepend comma
          cpuxtra = cfg.lines[i + 2].gsub("implementation", 'impl').gsub(/^/, ', ').chomp
        end
        comments << "CPU:#{slave} #{cpu}#{cpuxtra}#{slaveslot}"
      end

      comments << "Image: #{Regexp.last_match(1)}" if line =~ /^System image file is "([^"]*)"$/
    end
    comments << "\n"
    comment comments.join "\n"
  end

  cmd 'show vtp status' do |cfg|
    cfg.gsub! /^$\n/, ''
    cfg.gsub! /Configuration last modified by.*\n/, ''
    cfg.gsub! /^/, 'VTP: ' unless cfg.empty?
    comment "#{cfg}\n"
  end

  cmd 'show inventory' do |cfg|
    comment cfg
  end

  post do
    cmd_line = 'show running-config'
    cmd_line += ' view full' if vars(:ios_rbac)
    cmd cmd_line do |cfg|
      cfg = cfg.each_line.to_a[3..-1]
      cfg = cfg.reject { |line| line.match /^ntp clock-period / }.join
      cfg = cfg.each_line.reject { |line| line.match /^! (Last|No) configuration change (at|since).*/ unless line =~ /\d+\sby\s\S+$/ }.join
      cfg.gsub! /^Current configuration : [^\n]*\n/, ''
      cfg.gsub! /^ tunnel mpls traffic-eng bandwidth[^\n]*\n*(
                    (?: [^\n]*\n*)*
                    tunnel mpls traffic-eng auto-bw)/mx, '\1'
      cfg
    end
  end

  cfg :telnet do
    username /^Username:/i
    password /^Password:/i
  end

  cfg :telnet, :ssh do
    # preferred way to handle additional passwords
    post_login do
      if vars(:enable) == true
        cmd "enable"
      elsif vars(:enable)
        cmd "enable", /^[pP]assword:/
        cmd vars(:enable)
      end
    end
    post_login 'terminal length 0'
    post_login 'terminal width 0'
    pre_logout 'exit'
  end
end

⇧ いずれも、戻り値をガン無視しとるように見えるんだが...

え~っと...

発狂しそうなんだが...

いや、Ruby、意味が分からな過ぎる...

と言うか、

qiita.com

⇧ return省略しなくても良くない?

returnの6文字を省略するぐらいなら、省略せずに書いたら駄目なの?

最早、ツッコミどころが多過ぎるのと、何がしたいのか全く分からないのとで、頭抱えるしかないんだが...

何か、普通に、

■model/helper/custom_ios_helper.rb

class CustomIOSHelper

  ################################################################
  #  ?
  #  @param cfg コマンド実行結果の文字列
  #  @return cfg 文字列
  ################################################################
  def self.take_all_info(cfg)
    # cfg.gsub! /\cH+\s{8}/, ''         # example how to handle pager
    # cfg.gsub! /\cH+/, ''              # example how to handle pager
    # get rid of errors for commands that don't work on some devices
    cfg.gsub! /^% Invalid input detected at '\^' marker\.$|^\s+\^$/, ''
    return cfg
  end

  ################################################################
  #  機密情報をマスクする処理
  #  @param cfg コマンド実行結果の文字列
  #  @return cfg 機密情報をマスクする処理実施後の文字列
  ################################################################
  def self.secret_hidden(cfg)
    cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>'
    cfg.gsub! /^(snmp-server host \S+( vrf \S+)?( informs?)?( version (1|2c|3 (noauth|auth|priv)))?)\s+\S+((\s+\S*)*)\s*/, '\\1 <secret hidden> \\7'
    cfg.gsub! /^(username .+ (password|secret) \d) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(enable (password|secret)( level \d+)? \d) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+(?:password|secret)) (?:\d )?\S+/, '\\1 <secret hidden>'
    cfg.gsub! /^(.*wpa-psk ascii \d) (\S+)/, '\\1 <secret hidden>'
    cfg.gsub! /^(.*key 7) (\d.+)/, '\\1 <secret hidden>'
    cfg.gsub! /^(tacacs-server (.+ )?key) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(crypto isakmp key) (\S+) (.*)/, '\\1 <secret hidden> \\3'
    cfg.gsub! /^(\s+ip ospf message-digest-key \d+ md5) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+ip ospf authentication-key) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+neighbor \S+ password) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+vrrp \d+ authentication text) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+standby \d+ authentication) .{1,8}$/, '\\1 <secret hidden>'
    cfg.gsub! /^(\s+standby \d+ authentication md5 key-string) .+?( timeout \d+)?$/, '\\1 <secret hidden> \\2'
    cfg.gsub! /^(\s+key-string) .+/, '\\1 <secret hidden>'
    cfg.gsub! /^((tacacs|radius) server [^\n]+\n(\s+[^\n]+\n)*\s+key) [^\n]+$/m, '\1 <secret hidden>'
    cfg.gsub! /^(\s+ppp (chap|pap) password \d) .+/, '\\1 <secret hidden>'
    return cfg
  end

  ################################################################
  #  対象機器から取得した情報を加工する処理
  #  @param cfg コマンド実行結果の文字列
  #  @return comments 加工後の文字列の配列
  ################################################################
  def self.take_machine_info(cfg)
    comments = []
    comments << cfg.lines.first
    lines = cfg.lines
    lines.each_with_index do |line, i|
      slave = ''
      slaveslot = ''

      if line =~ /^Slave in slot (\d+) is running/
        slave = " Slave:"
        slaveslot = ", slot #{Regexp.last_match(1)}"
      end

      comments << "Image:#{slave} Compiled: #{Regexp.last_match(1)}" if line =~ /^Compiled (.*)$/

      comments << "Image:#{slave} Software: #{Regexp.last_match(1)}, #{Regexp.last_match(2)}" if line =~ /^(?:Cisco )?IOS .* Software,? \(([A-Za-z0-9_-]*)\), .*Version\s+(.*)$/

      comments << "ROM Bootstrap: #{Regexp.last_match(3)}" if line =~ /^ROM: (IOS \S+ )?(System )?Bootstrap.*(Version.*)$/

      comments << "BOOTFLASH: #{Regexp.last_match(1)}" if line =~ /^BOOTFLASH: .*(Version.*)$/

      comments << "Memory: nvram #{Regexp.last_match(1)}" if line =~ /^(\d+[kK]) bytes of (non-volatile|NVRAM)/

      comments << "Memory: flash #{Regexp.last_match(1)}" if line =~ /^(\d+[kK]) bytes of (flash memory|flash internal|processor board System flash|ATA CompactFlash)/i

      comments << "Memory: pcmcia #{Regexp.last_match(2)} #{Regexp.last_match(3)}#{Regexp.last_match(4)} #{Regexp.last_match(1)}" if line =~ /^(\d+[kK]) bytes of (Flash|ATA)?.*PCMCIA .*(slot|disk) ?(\d)/i

      if line =~ /(\S+(?:\sseries)?)\s+(?:\((\S+)\)\s+processor|\(revision[^)]+\)).*\s+with (\S+k) bytes/i
        sproc = Regexp.last_match(1)
        cpu = Regexp.last_match(2)
        mem = Regexp.last_match(3)
        cpuxtra = ''
        comments << "Chassis type:#{slave} #{sproc}"
        comments << "Memory:#{slave} main #{mem}"
        # check the next two lines for more CPU info
        comments << "Processor ID: #{Regexp.last_match(1)}" if cfg.lines[i + 1] =~ /processor board id (\S+)/i
        if cfg.lines[i + 2] =~ /(cpu at |processor: |#{cpu} processor,)/i
          # change implementation to impl and prepend comma
          cpuxtra = cfg.lines[i + 2].gsub("implementation", 'impl').gsub(/^/, ', ').chomp
        end
        comments << "CPU:#{slave} #{cpu}#{cpuxtra}#{slaveslot}"
      end

      comments << "Image: #{Regexp.last_match(1)}" if line =~ /^System image file is "([^"]*)"$/
    end
    comments << "\n"
    return comments.join "\n"
  end

  ################################################################
  #  対象機器から取得した設定情報を加工する処理
  #  @param cfg コマンド実行結果の文字列
  #  @return comments 加工後の文字列
  ################################################################
  def self.take_machine_config_info(cfg)
      cfg = cfg.each_line.to_a[3..-1]
      cfg = cfg.reject { |line| line.match /^ntp clock-period / }.join
      cfg = cfg.each_line.reject { |line| line.match /^! (Last|No) configuration change (at|since).*/ unless line =~ /\d+\sby\s\S+$/ }.join
      cfg.gsub! /^Current configuration : [^\n]*\n/, ''
      cfg.gsub! /^ tunnel mpls traffic-eng bandwidth[^\n]*\n*(
                    (?: [^\n]*\n*)*
                    tunnel mpls traffic-eng auto-bw)/mx, '\1'
      return cfg
  end
end    

■model/custom_ios.rb

require_relative 'custom_ios_helper'

class CustomIOS < Oxidized::Model

  using Refinements

  prompt /^([\w.@()-]+[#>]\s?)$/
  comment  '! '

  
  cmd :all do |cfg|
    cfg = CustomIOSHelper.take_all_info(cfg)
    cfg.cut_both
  end

  cmd :secret do |cfg|
    # 機密情報をマスクする
    cfg = CustomIOSHelper.secret_hidden(cfg)  
    return cfg
  end

  cmd 'show version' do |cfg|
    # 対象機器の情報を
    comment = CustomIOSHelper.take_machine_info(cfg)
  end

  cmd 'show inventory' do |cfg|
    comment cfg
  end

  post do
    cmd_line = 'show running-config'
    cmd_line += ' view full' if vars(:ios_rbac)
    cmd cmd_line do |cfg|
      cfg = CustomIOSHelper.take_machine_config_info
      return cfg
    end
  end

  cfg :telnet do
    username /^Username:/i
    password /^Password:/i
  end

  cfg :telnet, :ssh do
    # preferred way to handle additional passwords
    post_login do
      if vars(:enable) == true
        cmd "enable"
      elsif vars(:enable)
        cmd "enable", /^[pP]assword:/
        cmd vars(:enable)
      end
    end
    post_login 'terminal length 0'
    post_login 'terminal width 0'
    pre_logout 'exit'
  end
end   

みたいに分離してしまえば良いような気がするんだが。

テストとかもし易くなるしで、個人的には分離してしまいたい派なんだが、Rubyだと処理を分離するのは御法度なのかね?

まぁ、

  • Ruby標準のメソッド
  • Ruby標準ではないメソッド

の区別が付き辛いので、分離がし辛いのかもしらんけど...

驚きなのが、

  • Ruby標準のメソッド
    cfg.gsub!
  • Ruby標準ではないメソッド
    cfg.cut_both

という感じで、知っていないと区別できなくない?っていう感じで、カオスの極致過ぎるんだわ...

他に、メソッドの区別が分かり辛過ぎて、分離が困難を極めますと...

冗談抜きで、Ruby、ストレスでしかなくて地獄なんだが...

Rubyカオス過ぎる説について「異論は認めない」と言ったとて、過言ではない気がしてしまうんだが...

たまたま、「Oxidized」のソースコードが酷過ぎただけなのか、Ruby全体のソースコードも同様な状況なのか、Rubyの思想が分からな過ぎるのだが、ソースコードのお手本が欲しい...

ちなみに、メソッドで戻り値が無い場合、所謂、JavaのvoidのようなことをRubyで実装する場合、どうすれば良いのか?

qiita.com

⇧ 上記サイト様がまとめて下さっておりました。

うむ、Ruby、カオスだわ...

毎度モヤモヤ感が半端ない…

今回はこのへんで。