ZhangHanDong / tao-of-rust-codes

《Rust编程之道》随书源码
https://ruststudy.github.io/tao_of_rust_docs/tao_of_rust/
MIT License
1.18k stars 170 forks source link

有关本书中代码的风格. (抱歉实在不能忍了,提个建议) #314

Closed zw963 closed 3 years ago

zw963 commented 3 years ago

抱歉这并不是一个勘误,而是个人一点小小的看法,

在阅读本书过程中,虽然有很多小的错误,以及 typo, 以及老的代码的写法,基本上对没有啥影响 我相信在下一版一定会修复,但是这个示例代码的风格,我实在是忍不了,想想还是提一下。

第七章,结构化编程章节,刚开始介绍了 结构体的面向对象编程,并给了一个 ColoredString 的例子。 然后,又介绍了 枚举体 的最用,并尝试将前一个例子使用枚举体改写。

这里,从我个人角度,要指出的问题是: (全书这样的问题很多,这应该也是对读者影响最大的吧)

后面这个例子突然,夹杂了大量的跟本章示例要讲的内容无关的内容,一下变的很长,本来我只是想 看这里如何讲解 从结构体重构为枚举体 的, 然而,我们的代码里突然夹杂了(只要对于现在章节来说) 大量无用的东西。

例如:

  1. 可以支持通过字符串设置颜色, 为啥讲这个?
  2. 为了引入 color 和 on_color 方法,又开始讲实现 From, 然后又引入 std::str::FromStr, Color 里面加了一大堆颜色的方法,如果只是希望介绍枚举的易用性,只多加一个 blue 就足够了,对吗?

作为一个对 Rust 不熟悉的人,看到这样的例子,立刻就迷失止步不前了, 我认为本书经常会这样,用很大的篇幅讲,在当前阶段,完全无法理解的东西,即使能理解,也搞的挺痛苦, 当我读到 From/Into 这里的时候,当时的感觉就是,这是干嘛呢这是? 这是 必要的吗? 我明明只想看下重构,干嘛让我又看 From/To, 以及一堆泛型函数。

最后的结果,就是看不懂 From/Into 这块儿,后面也看不下去,最后完全不看,你辛辛苦苦举的例子也浪费了.

就以当前例子来说,代码完全可以精简,删掉一小半, 包含最小限度的知识点,能说明本章节的问题,就够了。

enum Color {
    Red,
    Yellow,
    Blue,
}

struct ColoredString {
    input: String,
    fgcolor: Option<Color>,
    bgcolor: Option<Color>,
}

impl Default for ColoredString {
    fn default() -> Self {
        ColoredString {
            input: String::default(),
            fgcolor: None,
            bgcolor: None,
        }
    }
}

impl Color {
    fn to_fg_str(&self) -> &str {
        match *self {
            Color::Red => "31",
            Color::Yellow => "33",
            Color::Blue => "34",
        }
    }

    fn to_bg_str(&self) -> &str {
        match *self {
            Color::Red => "41",
            Color::Yellow => "43",
            Color::Blue => "44",
        }
    }
}

trait Colorize {
    fn red(self) -> ColoredString;
    fn blue(self) -> ColoredString;
    fn on_yellow(self) -> ColoredString;
}

impl Colorize for ColoredString {
    fn red(self) -> ColoredString {
        ColoredString {
            fgcolor: Some(Color::Red),
            ..self
        }
    }

    fn blue(self) -> ColoredString {
        ColoredString {
            fgcolor: Some(Color::Blue),
            ..self
        }
    }

    fn on_yellow(self) -> ColoredString {
        ColoredString {
            bgcolor: Some(Color::Yellow),
            ..self
        }
    }
}

impl<'a> Colorize for &'a str {
    fn red(self) -> ColoredString {
        ColoredString {
            fgcolor: Some(Color::Red),
            input: String::from(self),
            ..ColoredString::default()
            }
        }

    fn blue(self) -> ColoredString {
        ColoredString {
            fgcolor: Some(Color::Blue),
            input: String::from(self),
            ..ColoredString::default()
        }
    }

    fn on_yellow(self) -> ColoredString {
        ColoredString {
            bgcolor: Some(Color::Yellow),
            input: String::from(self),
            ..ColoredString::default()
        }
    }
}

impl ColoredString {
    fn compute_style(&self) -> String {
        let mut res = String::from("\x1B[");
        let mut has_wrote = false;

        if let Some(ref bgcolor) = self.bgcolor {
            if has_wrote {
                res.push(';');
            }
            res.push_str(bgcolor.to_bg_str());
            has_wrote = true;
        }

        if let Some(ref fgcolor) = self.fgcolor {
            if has_wrote {
                res.push(';');
            }
            res.push_str(fgcolor.to_fg_str());
        }

        res.push('m');
        res
    }
}

use std::fmt;

impl fmt::Display for ColoredString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let input = &self.input.clone();
        f.write_str(&self.compute_style())?;
        f.write_str(input)?;
        f.write_str("\x1B[0m")?;
        Ok(())
    }
}

fn main() {
    let hi = "Hello".red().on_yellow();
    println!("{}", hi);
    let hi = "Hello".on_yellow();
    println!("{}", hi);
    let hi = "Hello".red();
    println!("{}", hi);
    let hi = "Hello".on_yellow().blue();
    println!("{}", hi);
}

然后简单的辅以讲解: (只是例子)

  1. 第一步,首先定义一个枚举体 Color,用来表示颜色。 使用 enum 表示颜色的好处是,可以随意增加枚举的 variant 来新增颜色.
  2. 第二步,定义一个结构体 ColoredString, 用来表示 `有颜色的字符串'. 它包含三个字段: input: 字符串内容 String fgcolor: 字体颜色, Option, bgcolor: 自体背景颜色 Option, 并为这个结构体实现 Default trait, 来为结构体的成员设定默认值.
  3. 因为我们需要支持链式调用,因此,需要为 ColoredString 和 &'a str 都实现 同样的方法,这正是 trait 大显身手的地方,因此我们引入一个 trait,Colorize, 并在这个 trait 中定义方法签名. red(), blue(), on_yellow(). 所有的方法均返回表示 `有颜色的字的对象', ColoredString.
  4. 然后分别为 ColoredString 和 &'a str 实现这个 trait.

以上均个人预见,如有冒犯,还望海涵。 谢谢。

ZhangHanDong commented 3 years ago

感谢你的反馈。

目前这样的内容构建,每一章其实并不是独立的,我也希望你能把前面章节的内容用起来,比如你说的trait。我写书假设和建议(在前言部分)读者是循序渐进来学习的。

作为写书作者,我要面对很多类似的读者,我不可能满足所有读者的阅读习惯,我只能从我的立意和知识布局出发去构建内容。并且 Rust 里零碎的知识其实挺多的,很多细节是我是通过例子来传递给读者的(比如你说的From/To)。

《如何阅读一本书》中提到过,作者和读者,就像是抛球和接球的双方。作者抛出一个球,读者想接到这个球,并没那么容易。有的读者可能正好接到了,有的正好没接到。

所以,我也建议你在阅读一本书的时候,也不要总是希望一本书写的正好符合你的思维习惯。(这只是单纯就读书这件事上的一个探讨)。

对于你的意见,我也会在第二版中考虑,再次感谢反馈。