首页 文章

水晶:确保返回值不是零

提问于
浏览
2

我有一个如下定义的辅助类:

require "toml"

module Test
  class Utils
    @@config

    def self.config
      if @@config.is_a?(Nil)
        raw_config = File.read("/usr/local/test/config.toml")
        @@config = TOML.parse(raw_config)
      end
      @@config
    end
  end
end

当我在代码中的其他地方调用此方法时:

server = TCPServer.new("localhost", Utils.config["port"])

我收到以下编译时错误:

in src/test/daemon.cr:10: undefined method '[]' for Nil (compile-time type is (Hash(String, TOML::Type) | Nil))

      server = TCPServer.new("localhost", Utils.config["port"])

Utils.config 无法运行 Nil ,所以我不明白错误 .

  • 如何告诉编译器 Utils.config 将始终返回不是 Nil 的内容?

  • (轻微的附加问题)这是一个很好的设计模式,适用于将在类之间共享的资源( config ),但只应创建一次?

2 回答

  • 1

    编辑:请参阅下面的JohannesMüller答案,这是一个更好的解决方案 .


    通常,如果要避免使用 Nil ,则应键入类和实例变量:

    @@config : Hash(String,Toml::Type)

    这将有助于编译器帮助您 - 通过查找可能导致 Nil 值的代码路径并在编译期间提醒您 .

    代码的潜在修复:

    require "toml"
    
    module Test
      class Utils
        @@config = {} of String => TOML::Type # This line prevents union with Nil
    
        def self.config
          if @@config.empty?
            raw_config = File.read("/usr/local/test/config.toml")
            @@config = TOML.parse(raw_config)
          else
            @@config
          end
        end
    
      end
    end
    
    puts Test::Utils.config["port"]
    

    由于toml要求,我无法直接测试,但是使用字符串的运行示例在这里:https://play.crystal-lang.org/#/r/30kl

    对于第二个问题,这种方法可能有效:

    require "toml"
    
    module Test
      class Utils
        CONFIG = TOML.parse(File.read("/usr/local/test/config.toml"))
      end
    end
    
    puts Test::Utils::CONFIG["port"]
    

    使用字符串而不是TOML的示例代码:https://play.crystal-lang.org/#/r/30kt

  • 4

    您的代码的问题在于,在if分支中检查 @config 是否为nil(这对于 @config.nil? 更容易),该实例变量的值可能已更改,直到它到达返回行 . 如果编译器从不同的光纤改变,它必须假设它可以再次为零 .

    您可以将其保存到本地变量并返回此值

    class Utils
      @@config
    
      def self.config
        if (config = @@config).nil?
          raw_config = File.read("/usr/local/test/config.toml")
          @@config = TOML.parse(raw_config)
        else
          config
        end
      end
    end
    

    或者有点重构,但基本上是相同的事情:

    class Utils
      @@config
    
      def self.config
        @@config ||= begin
          raw_config = File.read("/usr/local/test/config.toml")
          TOML.parse(raw_config)
        end
      end
    end
    

    我更喜欢在默认初始化时使用空对象将 @@config 设置为 nil ,因为它清楚地表明此对象不可用 . 例如,如果配置文件恰好为空,则检查 empty? 将始终触发重新加载和解析,从而消除了memoization功能 .

    ||= 运算符基本上意味着

    if config = @@config
      config
    else
      @@config = # ...
    end
    

相关问题