阅读指南 – 精炼 Ruby 编程 – 第 1 章
第 1 章 — — 充分利用核心课程
《精炼 Ruby 编程》是Jeremy Evans最近出版的一本书,他是一位知名的 Ruby 程序员,参与了 Ruby 核心团队、Roda、Sequel 以及其他多个项目的开发。由于了解 Jeremy 以及他丰富的经验,我立刻就买了这本书,并且非常期待这本书的内容。
您可以在这里找到这本书:
https://www.packtpub.com/product/polished-ruby-programming/9781801072724
这篇书评与以往其他“一起阅读”系列一样,将逐章逐节逐行解读,并添加评论、补充说明和对内容的总体看法。请记住,书籍在每页纸上所能容纳的信息量是有限的,不可能涵盖所有内容。
话虽如此,让我们继续开始吧。
第 1 章 — — 充分利用核心课程
本书首先概述了核心课程以及以下主题:
- 了解何时使用核心类
- true、false 和 nil 对象的最佳用途
- 满足不同需求的不同数字类型
- 了解符号与字符串的区别
- 学习如何最好地使用数组、哈希和集合
- 使用 Struct —— 未被充分重视的核心类之一
我们将逐一介绍这些内容。乍一看,这是对 Ruby 中常见令人困惑的主题的一个很好的概述。
了解何时使用核心类
我们从两个示例开始,一个使用Array
,另一个使用自定义类ThingList
:
things = ["foo", "bar", "baz"]
things.each do |thing|
puts thing
end
things = ThingList.new("foo", "bar", " baz")
things.each do |thing|
puts thing
end
这里要指出的是,第一种比第二种更清晰。ThingList
与更为人熟知的相比,使用 会带来很多不确定性Array
,尤其是因为正如前面提到的,为什么有人会用 而不是 呢Array
?
关于扩展核心类以及其中可能发生的一些不良后果,有很多讨论,其中一篇特别的讨论是Michael Herold 的“让我们子类化哈希——最糟糕的情况是什么?”。简而言之,这个Hashie
gem 尝试实现点访问(hash[:a]
也可以称为 as hash.a
),但随之而来的是各种各样的问题。
杰里米在这里的观点是正确的:只有当你知道风险和所得利益大于风险时,才可以采用定制。
性能、直观理解、可维护性等风险经常出现,应该予以考虑。
true
、false
和nil
对象的最佳用途
真假
true
和false
是相当通用的概念,正如前文所述,如果它们满足你的需求,就应该使用它们。然而,需要注意的是,它们是 和 的实例,TrueClass
除非你使用像 Steep 或 Sorbet 这样的语法,FalseClass
否则 Ruby 实际上并没有 的概念Boolean
。
第一种使用它们的情况是谓词方法,或者?
在 Ruby 中以 结尾的方法:
1.kind_of?(Integer)
# => true
给出的其他例子涉及等式和不等式:
1 > 2
# => false
1 == 1
# => true
注意:
===
在 Ruby 中的行为非常不同,但这是以后讨论的主题
对我来说,关键在于你是否在回答一个问题。对于谓词方法来说,答案很明确,但对于等式和不等式来说,答案可能不那么明确。另一个常见的用法是状态更新,比如某件事是成功了还是失败了?当然,这些用法通常更多地使用元组类型的对,比如[true, response]
or [false, error]
,但这个话题留到以后再说。
零
接下来他介绍nil
一些常见的用法:
[].first
# => nil
{1=>2}[3]
# => nil
nil
应该理解为什么都没有,当没有东西可返回时,我们就返回它。在第一种情况下,没有 的第一个元素Array
;在第二种情况下,没有 的键3
。
注意:
Hash
可以通过Hash.new(0)
或分配一个默认值Hash.new { |h, k| h[k] = [] }
,以覆盖“没有”的想法,但这超出了这里的观点。
棘手的部分(也是之前提到的部分)!nil
是true
和!1
:false
!nil
# => true
!1
# => false
这让我们得到像这样的模式来“强制”Boolean
类似的价值观:
!!nil
一般来说nil
,除非真的“什么都没有”,否则应该避免这种情况。考虑一下这种情况:
[1, 2, 3].select { |v| v > 4 }
# => []
当然,我们发现“什么也没有”,但更好的答案是空,Array
也就是这个特殊情况下的“什么也没有”。如果我们返回nil
并尝试这样做,你认为会发生什么?
[1, 2, 3].select { |v| v > 4 }.map { |v| v * 2 }
你可能会遇到一些错误。在这种特殊情况下,[1, 2, 3]
那里没有“任何东西”,但在其他情况下,例如[4, 5, 6]
?,这是有效的。你可能会注意到这里有一些带有“空”或“无”值的模式,但这很容易进入函数式编程的领域,这是一个非常有趣的想法,如果你特别喜欢冒险,可以在这里阅读更多内容。
重点是,返回合理的默认值,而不是nil
有意义的值。
Bang( !
) 方法和 Nil
接下来是 Ruby 中一些比较令人困惑的部分,特别是围绕 bang ( !
) 方法:
"a".gsub!('b', '')
# => nil
[2, 4, 6].select!(&:even?)
# => nil
["a", "b", "c"].reject!(&:empty?)
# => nil
Jeremy 提到,这样做是为了优化,确保接收方不会进行修改。对我来说,这也是我避免!
频繁使用某些方法的原因,因为我不止一次遇到过这种情况,而且很多时候你真的不需要它们。我的一般规则是,除非绝对必要,否则避免修改和修改方法,因为这会破坏链式调用以及对 Ruby 工作原理的很多直觉理解。
使用 false 和 nil 进行缓存
在提供的两个示例中:
@cached_value ||= some_expression
# or
cache[:key] ||= some_expression
如果some_expression
是false
,则nil
它会重新评估,而不是“缓存”以供以后使用。建议的替代方案是使用defined?
:
if defined?(@cached_value)
@cached_value
else
@cached_value = some_expression
end
就我个人而言,我倾向于使用基于方法的缓存的守卫式语句,但这是一个偏好问题:
def another_expression
return @cached_value if defined?(@cached_value)
@cached_value = some_expression
end
哈希缓存
他还提到了Hash
使用 es 进行缓存,fetch
它还有一些有趣的行为:
cache.fetch(:key) { cache[:key] = some_expression }
有几种方法可以fetch
完成一些重要的事情,值得在这里提一下:
hash = { a: 1 }
# => {:a=>1}
hash.fetch(:a)
# => 1
hash.fetch(:b, 1)
# => 1
hash.fetch(:b) { 1 }
# => 1
hash.fetch(:b)
# KeyError (key not found: :b)
如果您fetch
对一个不存在的值没有使用默认或提供的块,它将引发一个KeyError
,这对于确保事物存在非常有用。
内存优势
最后需要补充的是,由于true
、false
和nil
是直接对象类型,它们的执行速度会比大多数其他 Ruby 对象更快。这意味着创建时无需分配内存,后续访问时也无需进行间接访问,因此它们比非直接对象更快。
满足不同需求的不同数字类型
接下来我们来看看不同的数字类型。Jeremy 开门见山地指出,在大多数情况下,你可能只需要一个Integer
类型,而不是分数类型。Ruby 还提供了浮点数、有理数和 BigDecimal 等一些非十进制类型的变量。它们都属于这个Numeric
类别。
注意: - 如上所述,
BigDecimal
默认情况下不需要:require 'big_decimal'
。它还有一个特别麻烦的兼容性问题,BigDecimal.new
会导致与 发生冲突BigDecimal()
。我仍然不明白他们为什么不保留它而只是给它起个别名,但可惜我们就是这样。
他首先举了一个例子times
:
10.times do
# executed 10 times
end
这里可能最好也包含块变量并指出它接收每个值:
3.times do |i|
puts i
end
# 0
# 1
# 2
...因为示例引用了for
循环等效性,这可能会导致一些混淆和引入计数器变量,其中已经内置了一个计数器变量来涵盖这种情况。
整数除法和截断
关于 s 的一个常见混淆点Integer
以及他在这里提出的一个混淆点是截断时会发生什么:
5 / 10
# => 0
7 / 3
# => 2
这可能并不是我们想要的结果,因此在将其中一个数字转换为不同的数字类型时要小心Rational
(因为Float
它有自己的一些乐趣,我们稍后会介绍。)
它只返回商,不返回余数或小数部分。这和 C 语言类似,而且挺有意思的,在某些公司面试时会问这个问题。
浮点数
书中提到的解决方法Rational
或Float
在这里:
# or Rational(5, 10) or 5 / 10.to_r
5 / 10r
# => (1/2)
# Float
7.0 / 3
# => 2.3333333333333335
Float
虽然被认为是最快的,但速度并不精确。这个网站对原因有一个很好的解释,但简而言之,就是没有足够的数字来表示所有数字,而且你对 进行的操作越多,Float
这一点就越明显,例如:
f = 1.1
v = 0.0
1000.times do
v += f
end
v
# => 1100.0000000000086
有理数
Rational
可以更精确地解决这个问题,但总体来说速度较慢。如果你处理的是金钱或其他需要精确计算的事情,那么使用这种方法Float
是个坏主意。
如果我们使用相同的代码来Rational
代替,书中会显示以下内容:
f = 1.1r
v = 0.0r
1000.times do
v += f
end
v
# => (1100/1)
现在就速度而言,Jeremy 提出了一个很好的观点,让人想起了 YAGNI(你不需要它)。它们的速度可能要慢 2-6 倍,而且微优化很少会成为代码的瓶颈。
正如他在书中提到的,理性在你需要精确答案的时候非常有用,而正如之前提到的,金钱绝对是其中之一。但如果你只是比较数字而不是进行计算呢?嗯,Float
可能没问题。
大十进制
那么,这个等式中还有什么意义呢BigDecimal
?让我们看一下提供的示例:
v = BigDecimal(1) / 3
v * 3
# => 0.999999999999999999e0
f = BigDecimal(1.1, 2)
v = BigDecimal(0)
1000.times do
v += f
end
v
# => 0.11e4
v.to_s('F')
# => "1100.0"
BigDecimal
正如其名称所示,它使用科学计数法,因此可以处理非常大的数字。本书并没有对此进行过多的详细介绍,而且坦白说,我自己在 Ruby 中也很少使用它们。
我个人很喜欢HoneyBadger发表的这篇关于货币以及何时BigDecimal
或Rational
可能使用货币的文章。
了解符号与字符串的区别
如果说 Ruby 中有一个问题比其他大多数问题加起来还要让人困惑,那就是Symbol
vsString
以及两者同时使用的情况。我对此有一些个人看法,但我会留到以后再说。
正如书中所述,Rails 会将它们一视同仁地处理,以此来避免这种烦人的问题,Hash#with_indifferent_access
从而避免关注它们之间的区别。正如书中所述,在后台,许多 Ruby 也会进行这种转换。
那么这两者是什么?
字符串
Ruby 中的字符串是一系列字符或字节,用于存储文本或二进制数据。除非字符串被冻结,否则可以向其添加内容、修改其中的现有字符或将其替换为其他字符串。
在大多数情况下,我都会主张冻结String
s,Ruby 甚至有冻结字符串文字注释来执行此操作,该注释位于文件顶部:
# frozen_string_literal: true
事实证明,这种方法可以提升应用程序性能,而且通常更容易操作,因为变异(尤其是在接收器上)可能会带来各种意想不到的后果。我们不会就此展开函数纯度之争,但一般来说,Ruby 中的变异方法会使代码推理更加困难,因此请谨慎使用。
我稍后会提到这一点,但如果冻结字符串文字是默认设置,那么很多用例Symbol
将变得更难以证明,尽管它们的实现仍然会带来一些边际性能提升。
象征
Ruby 中的符号是一个附加了标识符的数字,该标识符是一系列字符或字节。Ruby 中的符号是 Ruby 内部类型的对象包装器,该类型称为ID,是一种整数类型。在 Ruby 代码中使用符号时,Ruby 会查找与该标识符关联的数字。内部使用ID类型的原因是,对于计算机来说,处理整数比处理一系列字符或字节要快得多。Ruby 使用ID值来引用局部变量、实例变量、类变量、常量和方法名称。
解释 可能有点复杂Symbol
,但确实涉及到一些重要的实现细节。更简单地说,Symbol
是用于描述 Ruby 代码某一部分的标识性文本。
例如,方法可以通过Symbol
表示其名称来识别,就像def add
可以:add
在程序的其他地方表示的那样,并传递send
给检索方法代码:
method = :add
foo.send(method, bar)
警告:我个人更喜欢
method_name
这里,因为method
它本身是一种可以method
通过名称获取的方法,这可能会造成混淆。
令人困惑的是,这也有效,正如书中提到的:
method = "add"
foo.send(method, bar)
正如书中所述,这是因为 Ruby 试图对程序员友善,而且说实话,我觉得它有点自知之明,知道这很容易让人困惑。许多String
方法都会在 上起作用Symbol
,这加剧了这种情况。
书中提到了以下几个例子:
def switch(value)
case value
when :foo
# foo
when :bar
# bar
when :baz
# baz
end
end
在本例中,我们使用s 作为标识文本,而不是文本本身。但是,Symbol
如果我们想用 s 来做某事,那就没有多大意义了:value
Symbol
def append2(value)
value.gsub(/foo/, "bar")
end
在这种情况下value
,它将作为 起作用String
,因此我们应该确保String
将 传递给它。
个人观点
我个人认为,如果对冻结字符串进行优化,它更适合用来替代Symbol
。无论它能带来多少性能提升,都不值得为用户带来困惑,因此应该避免。
例如,Javascript 具有与 Ruby 相同的 JSON 类语法,但将键视为String
值:
const map = { a: 1, b: 2, c: 3 };
map['a'] // => 1
map.a // => 1
当然,正如 RubyConf 上的上述讨论中所提到的,后期的点语法在 Ruby 中是一个非常糟糕的Hashie
想法,但那是另一回事。
我主要的抱怨是,尽管 Ruby 赋予了Symbol
它的使用价值,但在很多情况下它却喜欢假装它们不存在并强制执行某些操作以防止用户遇到错误。
无论如何,个人的抱怨已经结束,我实际上也不认为这种改变会在该语言的未来版本中发生,因为这将是一个太大的突破性变化,不值得社区为此付出迁移的痛苦。
学习如何最好地使用数组、哈希和集合
要讲的内容太多了,说实话,一章根本不够涵盖 Ruby 中那些有趣的部分Array
,但这也不是本书的重点,所以我跑题了。我强烈建议至少阅读一下本章之后的官方文档Enumerable
,了解一下 Ruby 的所有可能性。
大批
[[:foo, 1], [:bar, 3], [:baz, 7]].each do |sym, i|
# ...
end
提供的示例是一组包含两项的元组来表示数据,除了块可以使用像sym
和i
这样的参数来解构值之外,这里没有太多可展示的内容。请注意,这里与 though 相比,有一个非常微妙的地方需要注意Hash
:这里可以有多个 实例:foo
,但在Hash
需要唯一键的 中只能有一个。
哈希
Hash 示例非常相似:
{ foo: 1, bar: 3, baz: 7 }.each do |sym, i|
# ...
end
书中提到,Array
从设计角度来看,该解决方案可能更正确,但更Hash
容易实现。我倾向于同意这一点,但上面提到的情况可能会变得复杂。
想象一下,如果你有一组来自 AWS 的标签,以Array
元组的形式存在,那么将其表示为 aHash
可能不是一个好主意。在决定如何用 Ruby 表达它时,请记住你的底层数据。
实现内存数据库
这是我见过的书中关于这两个方法的一个比较独特的应用,我很喜欢他在这里尝试了一些更实质性的内容。他首先生成了一些模拟数据来演示:
album_infos = 100.times.flat_map do |i|
10.times.map do |j|
["Album #{i}", j, "Track #{j}"]
end
end
需要注意的是,flat_map
映射(转换)集合后会变平,但本书确实假设读者具备中级 Ruby 知识。
创建索引 - 数组元组
第一部分涉及索引数据,或者提供一种清晰的方法,可以从多个角度查找数据。如果我们要为其创建一个简单的索引函数,Array
可能如下所示(Rails 也做了类似的事情):
class Array
def index_by(&block)
indexes = {}
self.each { |v| indexes[block.call(v)] = v }
indexes
end
end
不过,请记住关于唯一键的那一点,因为这确实会让事情变得复杂。如果它用一个人的名字来索引,但两个人的名字相同怎么办?总之,回到他们提供的问题解决方案:
album_artists = {}
album_track_artists = {}
album_infos.each do |album, track, artist|
(album_artists[album] ||= []) << artist
(album_track_artists[[album, track]] ||= []) << artist
end
album_artists.each_value(&:uniq!)
当然,我可能会做类似这样的事情:
album_artists = Hash.new { |h, k| h[k] = Set.new }
album_track_artists = Hash.new { |h, k| h[k] = Set.new }
album_infos.each do |album, track, artist|
album_artists[album].add artist
album_track_artists[[album, track]].add artist
end
...这避免了混淆默认分配和以后的唯一性约束,因为Set
只能具有唯一值,但这也使得解决方案在第一章中更加复杂和难以解释,所以我可以理解为什么这样写。
查找功能很有趣:
lookup = -> (album, track = nil) do
if track
album_track_artists[[album, track]]
else
album_artists[album]
end
end
为什么?我们的第一个直觉可能是创建一个如下方法:
def lookup(album, track = nil)
# ...
end
……但它究竟在哪里得到“然后”呢album_artists
?album_track_artists
这个解决方案通过使用 lambda 函数来避免这个问题,lambda 函数通过所谓的闭包来捕获它们定义的本地上下文。
当然,我认为这在 Ruby 中有点不常见,也不太常用,但它避免了把所有这些都封装在一个类里,从而大大延长章节长度。不过我不确定在其他地方是否会推荐这样做。
(您还会注意到,在文章的篇幅内,我特意不亲自实现它)
创建索引 - 嵌套哈希
第二种解决方案是使用嵌套哈希:
albums = {}
album_infos.each do |album, track, artist|
((albums[album] ||= {})[track] ||= []) << artist
end
...和前一种情况一样,通过将该代码提升到初始对象实例,可能值得将赋值和默认值分离:
albums = Hash.new do |h, k|
h[k] = Hash.new { |h2, k2| h2[k2] = [] }
end
它是不是不够简洁?当然,但它也明确了我们数据的形状,我认为这是一个很好的权衡。
正如书中所提到的,查找代码变得更加复杂:
lookup = -> (album, track = nil) do
if track
albums.dig(album, track)
else
a = albums[album].each_value.to_a
a.flatten!
a.uniq!
a
end
end
我喜欢这本书的一点是,Jeremy 提到了每种方法的利弊。Array
元组方法占用更多内存,但查找大量记录时速度更快。第二种方法在album
查找方面效率低得多,但在嵌套查询方面表现出色。
创建索引 - 已知数据
不过,他在下一部分中所做的是对了解底层数据及其为我们带来什么的有趣见解。
albums = {}
album_infos.each do |album, track, artist|
album_array = albums[album] ||= [[]]
album_array[0] << artist
(album_array[track] ||= []) << artist
end
albums.each_value do |array|
array[0].uniq!
end
与前几节不同,本例假设第一项是艺术家,1
第二项99
是曲目。我们可以明确地对数据进行建模,但那样会变得相当混乱:
TRACK_COUNT = 99
albums = Hash.new { |h, k| h[k] = [Set.new, *([] * TRACK_COUNT)]}
...我并不特别喜欢,但确实暴露出这种数据结构有点危险。
这里的一个技巧是,Ruby 的dig
函数可以与 和 一起使用Hash
,Array
这意味着编号索引在这里可以工作,从而使查找函数更加简单:
lookup = -> (album, track = 0) do
albums.dig(album, track)
end
……但与其他两种方法不同,由于代码与数据结构紧密相关,因此在需求发生变化时可能会变得脆弱。虽然可以在这里获得一些额外的性能,但如果以后需要重新审视和重构,那么这样做可能就不值得了。
知名艺术家姓名 - 数组
下一部分想要开发一项功能,用于在专辑中查找知名艺术家的姓名,而不是用户提供的列表:
album_artists = album_infos.flat_map(&:last)
album_artists.uniq!
lookup = -> (artists) do
album_artists & artists
end
知名艺术家姓名 - Hash
……但提到,如果艺术家数量较多,这种方法可能会很慢。建议的解决方案是使用一个Hash
键值来标记知名艺术家:
album_artists = {}
album_infos.each do |_, _, artist|
album_artists[artist] ||= true
end
lookup = -> (artists) do
artists.select do |artist|
album_artists[artist]
end
end
尽管这可能更容易values_at
:
lookup = -> (artists) do
album_artists.values_at(*artists)
end
知名艺术家姓名 - 套装
...但这个练习的目的是引导我们Set
,所以让我们开始吧:
require 'set'
album_artists = Set.new(album_infos.flat_map(&:last))
lookup = -> (artists) do
album_artists & artists
end
区别在于,Set
前者比后者快得多Array
,但不如后者快Hash
。本书推荐前者,因为它有更好的 API;而后者,如果你需要性能提升,则推荐后者。
使用 Struct —— 未被充分重视的核心类之一
瞧,我真的很喜欢Struct
,尤其是在 REPL 里。很高兴在这里看到它。Jeremy 先从这里一个普通类的例子开始:
class Artist
attr_accessor :name, :albums
def initialize(name, albums)
@name = name
@albums = albums
end
end
如果你曾经觉得其中很多都是多余的,你一定会喜欢Struct
:
Artist = Struct.new(:name, :albums)
...尽管我个人喜欢使用 kwargs 来表示类,以便清楚地了解你传递给它的内容,并且Struct
还涵盖了这种情况:
Artist = Struct.new(:name, :albums, keyword_init: true)
Artist.new(name: 'Brandon', albums: [])
对我来说更清楚了。总之,书里提到了权衡,它Struct
比 a 更轻量class
,但查找属性需要更长的时间。
他确实提到了一个有趣的特性Struct
,一个新的实例实际上是一个Class
:
Struct.new(:a, :b).class
# => Class
子类化结构体
尽管对于所提到的子类来说情况并非如此:
Struct.new('A', :a, :b).new(1, 2).class
# => Struct::A
...他还指出了该Struct.new
方法的实现方式:
def Struct.new(name, *fields)
unless name.is_a?(String)
fields.unshift(name)
name = nil
end
subclass = Class.new(self)
if name
const_set(name, subclass)
end
# Internal magic to setup fields/storage for subclass
def subclass.new(*values)
obj = allocate
obj.initialize(*values)
obj
end
# Similar for allocate, [], members, inspect
# Internal magic to setup accessor instance methods
subclass
end
如果你恰好传递了类似的名称,'A'
它会在当前命名空间中定义一个常量,并将该子类附加到它上面。这里需要一些底层细节的说明,这肯定需要一些时间,然后是最后一节,讲解如何实际创建一个新的实例。
就我个人而言,我宁愿避免这种情况,而倾向于后面提到的子类化:
class SubStruct < Struct
end
Struct
...对于大多数情况下您需要了解的内容来说,上面的代码可能有点多。
冻结结构
下一节将提到自动冻结结构:
A = Struct.new(:a, :b) do
def initialize(...)
super
freeze
end
end
……这使得值不可变。Jeremy 还提到,Ruby 跟踪器上曾提交过几个问题,希望将其完善,但都没有被纳入 Ruby 3,而这是最可行的解决方法。
我个人喜欢使用不可变的小数据类型(如 Haskell 和 Scala 案例类)的想法,以便快速用作数据容器而不是域对象。
总结和问题
本章以总结和一些问题结束。让我们快速回顾一下这些问题。
1. nil和false与所有其他对象有何不同?
nil
实际上什么也不是,你在 Ruby 中经常看到的错误是由于进入了应用程序不期望的地方而导致的。
false
是 的一个实例FalseClass
,所以当我将它与 并列时,我不确定我是否理解了这个问题的意图nil
。或许用以下表述会更好,更能说明这些数据类型的意图?
2. 使用两个 BigDecimal 对象的所有标准算术运算都是精确的吗?
就两种BigDecimal
类型而言是的,但如果Float
偏向一侧则不会那么多。
3. Ruby 将符号和字符串结合起来有意义吗?
从哲学角度来说?我想Symbol
放弃它,因为它让 Ruby 新手的操作变得异常复杂,却几乎没有什么实际收益,甚至还让我时不时地犯错。我不喜欢它们,因为它们给语言带来了复杂性。
务实吗?不。应该保持原样,因为修改后的后果会破坏大量 Ruby 代码,并在社区引发一场大战。虽然我不喜欢,但这样做不值得。
4. 对于相同的数据,hash 和Set哪个占用的内存更少?
可能吧Hash
,但差别不大。我记得好像Set
是用 a 来实现的Hash
,所以应该不会差太多。
5.返回Class新实例的仅有的两个核心方法是什么?
Struct.new
我Class.new
会这么想。
包起来
优点
总的来说?实用主义。Jeremy 擅长权衡利弊,并解释为什么某些事情要以某种方式完成,这在他的很多作品中都有所体现。这是最好的解决方案吗?也许不是,但它能考虑到极端情况,而这正是他真正擅长的地方:深入挖掘这些细节。
本书以务实的立场探讨了不同数据结构及其用法对性能的影响。这样做的书籍并不多。
我花了一些时间来解决 Ruby 社区中的一个棘手问题Symbol
,String
并得到了一个相当合理的回应。我本来想看看删除其中一个问题会带来什么影响,但我明白,这会让本章的篇幅迅速膨胀。
它在入门问题上采取了更大胆的立场album
,这为探索有趣的代码提供了更多机会。太多示例感觉非常基础,并没有真正展现出很多潜在的问题,我认为这本书在这方面做得很好。
坏
Safari Books Online 有一个抢先体验版,所有代码都换行,并采用衬线字体,没有高亮显示。我希望 Packt 能解决这个问题,因为这样几乎无法阅读。我真心希望实体书也能解决这个问题。
就这本书本身而言,我觉得第一章试图将大量内容放在一章中,如果将其分成更多部分可能会更好。
true
我确实希望,false
和部分nil
更多地讨论合理的默认值,而不是像现在这样深入研究 bang 方法,因为这些方法在许多 Ruby 程序中会更有用,可以防止出现错误。
一些示例倾向于混淆赋值和连接行为,并且可能通过在代码之上明确定义数据结构来获得更好的效果||=
。
该部分Struct
从非常有用的概述转向了有点冗杂的内容,让我感到困惑。
概述
我打算继续阅读和编写其他章节的类似内容,并期待接下来的内容。
我对某些内容有异议吗?当然有,但我对上个月自己编写的代码有异议,我只是确保理解做出这些决定的原因,并尽可能记录相关因素。这些评论的乐趣在于提供额外的背景信息,并探讨某些主题被涵盖的原因。
第二章再见!
鏂囩珷鏉ユ簮锛�https://dev.to/baweaver/let-s-read-polish-ruby-programming-2mh5