读《代码里的世界观》

read books

Posted by 宸笙 on January 20, 2019

一段时间没关注新书后面看到怕错过好东西就赶紧下单看完,看后觉得挺不错,至少比起一些知识点的堆砌凑篇幅和蹭技术热点的书,此书算是挺好,很多是经验和心得,这才是我很想了解的;

简介

从在学校写代码到工作数年,慢慢会有这样的体会,学会和掌握什么技能对你来说越来越轻松,但你总有一些技术上的困惑和迷茫,怎样才能持续进步,在新框架新语言等层出不穷的时候自己要作何选择,在相对浮躁的技术圈怎样保持自己的节奏,在这些变化很频繁的东西背后,有没有另一些相对本质的能力需要自己去掌握并更好的应对多变的技术趋势;你也会逐渐明白在平时工作中经常开发新功能,使用新框架,随着这些工作量的积累,很多时候对自身而言仅仅是熟练程度的提升或是对bug更加从容,但还是会疑惑难道写代码就一直是这样吗,大部分时间在赶业务需求而没什么时间思考,一些老生常谈的内功好像也没怎么用上,难道真只是面试时候被用到的套路和讨论造火箭用的技巧?市面上和网络上也比较少这些东西,毕竟基础的东西比较小众,而一些大部头的书籍在平时可能难静下心来啃,而今天要推荐的这书切切是兼顾了易读性和紧扣编码本质两方面;

这本书叫《代码里的世界观》,当然副标题就先不说,毕竟从业经验没有很多年笔者也不妄谈Architect了,作者是IBM的架构师,写书的初衷在于写一本能让处于前面说的困惑的程序员一些启发思路,也定位于能常伴于你的书架,因为讲上层框架使用的书有的是蹭技术热点,时过境迁,很可能后面没用上了,而基础的书籍是能常伴我们的开发生涯的,而且不同阶段的你去理解书中的一些观点的共鸣深度是不同的,就像我看完一遍还想看,随着经验积累过后还会继续看;

书中从基础的编码概念和常用的语言构造讲起,从看似简单和司空见惯的概念中,注入了作者自身的理解和经验沉淀,会感慨原来还有这种理解,我之前司空见惯以为简单的东西在作者的笔下有了一些深刻的理解;除此之外,作者也会分析一些语言构造和语法糖,让读者对语言的一些惯用技巧和意图能逐渐入门,而这会让你对新语言和新框架都能快速学会并从容应对;当然了,最重要的是作者的笔法熟练,此书不难读,很顺畅但也回味不错;

一句话说,讲心法的书很少,如果想对编码和语言本质精进一些的程序员可以看下,新手可以很快入门一些朴实但很受用的知识(如design patterns,当年在实验室的我如果能看到此书就好了,不会如同现在自己摸索得来后知后觉),对于老手也能收获一些总结和共鸣;

一些观点与理解

  • 追求技术风潮背后不忘思考代码朴素的本质,不至于碌碌追新乏味缺乏思考,后面随着能力提升(比如你能很熟练模拟一些知名三方库)会正向反馈提升兴趣,Architector之路越走越远;
  • 代码世界的一些客观规律,好奇内在本质,简单技术也有不简单的妙用;
  • 基于现在软件分工和开发方式,大多数程序员都在调用语言或框架和类库提供的API,处于复用性和效率考虑很多已经被高手实现好了,但是我们还是要提升怎么去把复杂的业务代码和API串起来编写的能力;
  • 关于学习,越到后面一些很内在的机理一般很小众,也更需要自己去悟(做中思考),逆向分析知名库的代码也有一定难度和成本,可以从别人思考沉淀(blog或书)学习得到,也有一些是在老手的脑子里(隐性),所以最好是多主动交流挖掘(刚毕业时亲身体会到)。
  • 程序世界的两个元素:数据代码,简单理解代码是处理数据的,代码执行过程也便是数据处理和流转过程,笔者想到的是当然如果代码也成为了可以执行的数据的时候,就很powerful了,这就是为什么很多动态脚本语言有那么多的所谓黑魔法为人们津津乐道,或是runtime支持很强的表现力或是直观表现为代码字符串能直接被执行(被下发执行联想到热更新领域用到的lua,或是js里面的eval()方法),这部分应该属于meta—programming的话题了。
  • 用面向对象(OO)的方式去理解代码世界,从大一接触C开始再到C++到后面才慢慢体会到为何需要OO这种利器,慢慢才会体会到在写代码并非快速实现功能,我们需要对复杂的业务进行合理建模划分,体现在我们的分层和创建对象关系(依赖?组合?一对多?)上,而也处于项目长期维护的需要,我们需要写的更有结构性,不仅是工程上的易于扩展(直接修改太多隐患),还是对同事和接手人的负责(更快读懂并上手项目),怎么把代码写的更好维护扩展,更好读的这个过程类似在讲故事,所以作者便说”好的程序员是安徒生”,在实现也许需求时候的思考也是自己打腹稿的过程,类似如eat(cat,fish)和cat.eat(fish)的差异,其实OO只是工业界为了提高生产力和可维护性(更接近人的表达习惯和直觉)才出现的手段,对于机器而言,不管是OO的还是面向过程的代码,亦或是汇编(汇编也是为了程序员助记),其实机器(cpu)才不管这些(底层无非是0101),这些特性和分野无非是为了程序员能快速上手开发和解决业务复杂度(architect亦然);
  • 从基础的数据类型到单一的对象再到复合的复杂对象,一路都是封装的过程,作者也很巧妙比喻为单细胞生物到高等生物,主流语言都是OO的封装数据和方法,古老的c语言只能封装数据,就是我们常见的struct,为何,不同阶段软件开发的发展不同,语言本身对一些通用特性的支持也有不同程度的差异;
  • 简单的publicprotectedprivate,
  • 继承
  • 多态
  • 面向抽象编程,消灭new的两种方式:IOC和factory pattern,而抽象到什么程度是由用户(业务)需求决定;
  • 耦合,数据之间,函数之间,数据和函数之间的相互耦合;
  • 双向依赖引起的耦合
  • 解耦的原则:让模块逻辑独立而完整;让外部桥接兼顾并兼容;
void updatePersons(Array persons){
    // shareDB 是全局变量
    shareDB.open();
    foreach(Person person in persons){
        update(person);
    }
    shareDB.close();
}

void update(Person person){
    String sql = person.generateInsertSQL();
    shareDB.executeSql(sql);
}

如果update是public的就不合理,生效的前提是shareDB处于open状态,这个条件约束是隐性的。

// 修改 把数据库资源参数化
void update(Person person,DBConnection openedDB){
    String sql = person.generateInsertSQL();
    openedDB.executeSQL(sql);
}

// 调用 
// 好处1:多了DBConnection参数,对调用者是一种有用的约束;
// 好处2:openedDB是有效的调用提示,传进来的需要处于open状态
foreach(Person person in persons){
    update(person,sharedDB);
}
Person person = new Person();
person.readBook(book);

// readBook()
void readBook(Book book){
    // 
    wearGlasses(this.mGlasses);
    read(book);
}
// 属性注入
person.mClass = new Glasses();
person.readBook(book);

// 构造函数注入
// 缺点:但其他行为(函数如eat()不一定需要glases)
public Person(Glasses glasses){
    this.mClass = glasses;
}
// 普通注入
void readBook(Book book,Glasses glasses){
    wearClasses(glasses);
    read(book);
}
// 调用
// 但有点浪费,其实只需要一个glasses就够了
person.readBook(book,new Glasses());

// 封装注入,but may be null
person.readBook(book,person.mClass);
// =>
public Glasses MyGlasses{
    get{
        if(this.myGlasses == null){
            this.myGlasses = new Glasses();
            return this.myGlasses;
        }
    }
}
// back code
void readBook(Book book){
    wearGlasses(this.MyGlasses);
    readBook(book);
}
// client call success!
person.readBook(book);
  • 数据类型,按生命周期划分有global(static),栈区数据(方法内的基础类型数据,调用完成即回收),堆区数据(通过new或者反射创建出来的),数据的内存回收,引用记数与可达性遍历;
  • 两个重要的数据容器:数组散列表;
public ConfigService{
    // singleton Code
}

public DBService(){
    // singleton Code
}

public NetworkService(){
    // singleton Code
}

// .....

// =>
Map<String,Object> container = new HashMap();
container.add("ConfigService",new ConfigService();
container.add("DBService",new DBService();
container.add("NetworkService",new NetworkService();

// => 
class ServiceKeys{
    public static String configService = "ConfigService";
    public static String dBService = "DBService";
    public static String networkService = "NetworkService";
}
// get service
ConfigService service = container[ServiceKeys.configService];
// set service
container.add(ServiceKeys.configService,new ConfigService());
// 其实Android中一些Service也是类似,也用了HashMap去实现singleton;
  • 描述数据的数据,元数据,XML,DSL;
  • 需要用到使用者(开发者)编写元数据的场景

    ORM的框架实现,框架生成代码的前提需要元数据的支持(这些都是特定的用户需求,开发框架时无法预先获取);

    反射

  • 数据驱动
// example1
boolean isVaildFileType(String type){
    boolean isValid = true;
    if(type == "mp4"){
        isValid = true;
    }else if(type == "txt"){
        isValid = true;
    }else if(type == "ppt") || (type == "pptx"){
        isValid = true;
    }else if(type == "pdf"){
        isValid = true;
    }else if(type == "xls") || (type == "xlsx"){
        isValid = true;
    }else if(type == "doc") || (type == "docx"){
        isValid = true;
    }
    return isValid;
}
// => 
String[] validTypeArray = {
    "mp4","txt","ppt","pptx","pdf","xls","xlsx","doc","docx"
};
boolean isValidType(String type){
    return validTypeArray.contains(type);
}

// example2
if(number == "123"){
    doSth();
}

// =>
if(number == "123" || number == "456"){
    doSth();
}
// =>
Array expectionNumbers = {
    "123","456"
};
boolean isExceptionNumber(String number){
    return exceptionNumbers.contains(number);
}

// => 
if(isExceptionNumber(number)){
    doSth();
}

// example3
void btnClick(Object sender){
    BaseClass obj = new Class1();
}
if(selectItem == "Class1"){
    return new Class1();
}else if(selectItem == "Class2"){
    return new Class2();
}
// ...

// =>
{
    btn1_tag :"Class1",
    btn2_tag :"Class2",
    btn3_tag :"Class3",
    // ...
    btn20_tag :"Class20",
}

BaseClass generateClass(String btnTag){
    String className = dict[btnTag;
    Class<?> curClass = className.forName(className);
    return curClass.newInstance();
}

// client call
void DropDownListSelected(String selectedItem){
    BaseClass obj = generateClass(selectedItem);
}
  • 数据驱动和反射,反射是将代码数据化。
  • 反射将模块间的绑定延迟到运行时,这决定了反射能实现类似插件化,而很多框架性的代码也能随处看到反射的应用;
  • 但design patterns缺很少和反射结合,因反射出现的时间晚于前者成为显学的时间,且早期由于性能问题没很流行开来;
  • 反射的劣势:破坏了封装影响阅读和测试(依靠IDE的分析和堆栈描述是看不到反射调用的痕迹的);
  • 对象之间的关系:继承组合依赖
// 对比组合和继承
interface ICalcuteSalary{
    double calcuteSalary(double baseSalary);
}

public class CalcJuniorSalary implements ICalcuteSalary{
    // ...
}

public class CalcSeniorSalary implements ICalcuteSalary{
    // ...
}

public class CalcManagerSalary implements ICalcuteSalary{
    // ...
}

public class Employee{
    // ...
    private ICalcuteSalary cs;
    public double calcuteSalary(){
        return cs.calcuteSalary(this.baseSalary);
    }
}

main{
    Employee employee = new Employee();
    employee.setCalSalary(new CalcJuniorSalary());
    sout(employee.calcuteSalary());
    // set other strategys
}

// 使用继承
public class JuniorEmployee{
    CalcJuniorSalary calcJuniorSalary;
    public double clacSalary(){
        return calcJuniorSalary(this.baseSalary);
    }
}

// ...
public class SeniorEmployee{
    // ...
}
public class ManagerEmployee{
    // ...
}

main{
    Employee employee = new JuniorEmployee();
    sout(employee.clacSalary());
    // new other employees
    
}

  • 双向依赖
public class Shape{
    void draw(Window window);
}

public class MyWindow:Window{
    public void draw(Shape shape){
        shape.draw(this);
    }
}

// visitor patterns
public abstract class Shape{
    abstract void draw();
}

public class Rectangle extends Shape{}
public class Circle extends Shape{}
public class Line extends Shape{}

// 给子类扩展一个新的方法zoomIn()添加放大的逻辑
public class Rectangle extends Shape{
    // zoomIn code
}
public class Circle extends Shape{
    // zoomIn code
}
public class Line extends Shape{
    // zoomIn code
}

// but 这样编译不过
foreach(Shape shape in shapes){
    zoomIn(shape);
}
// 需要判断并转换类型
foreach(Shape shape in shapes){
    if(shape instanceOf Rectangle){
        zoomIn((Rectangle)shape);
    }else if(shape instanceOf Circle){
        zoomIn((Circle)shape);
    }(shape instanceOf Line){
        zoomIn((Line)shape);
    }
}
// 解决
public abstract class Shape{
    abstract void draw();
    abstract void acceptVisitor(IVisitor visitor);
}

interface IVisitor{
    void visit(Rectangle rect);
    void visit(Circle circle);
    void visit(Line line);
}

class ZoomInVisitor implements IVisitor{
    void visit(Rectangle rect){
        // ...
    }
    
    void visit(Circle circle){
        // ...
    }
    
    void visit(Line line){
        // ...
    }
}

class Rectangle extends Shape{
    public void draw(){
        // ...
    }
    
    public void acceptVisitor(IVisitor visitor){
        // 产生了双向依赖
        visitor.visit(this);
    }
}

// client call
main{
    IVisitor visitor = new ZoomInVisitor();
    foreach(Shape shape in shapes){
        shape.acceptVisitor(visitor);
    }
}
// visitor 和 double dispatch dynamic特性

  • 函数,参数是函数的原材料,传值,有状态函数和无状态函数,静态函数和普通成员函数,函数重载,泛型函数(泛型的实现:类型擦除还是模板技术)
  • 面向接口编程;
  • 使用接口的几大场景:

    先签约,后对接

    专注抽象

    解耦

  • 场景1: 面向同一接口,多模块(接口的两端)能互不影响(不用相互等待和持有引用)并行开发

  • 场景2:支持子类变化,先做抽象(sort() comparer接口)
  • 场景3:解耦MVC的层次或父子控件层

  • 接口函数指针
interface Command{
    void execute();
}
class LightOnCommand implements Command{
    Light light;
    public void execute(){
        if(light != null){
            light.turnOn();
        }
    }
}

class User{
    ICommand[] commandList;
    ICommand currentCommand;
    // setCommand 
    public void triggerCmd(){
        currentCommand.execute();
        commandList.add(currentCommand);
    }
}

main{
    User user = new User();
    user.setCmd(new LightOnCommand());
    user.triggerCmd();
}

  • interface 和 异步 callback

  • static实现类扩展

基本功的重要性慢慢变得更加重要,比如最近在模仿知名三方库的实现便深有体会,基本功更扎实写也能更好的逆向分析代码背后的思路和意图;