读《浪潮之巅》

资本主义

《浪潮之巅》的开篇讲的是 AT&T 的衰落,一家相当于华为+中国移动联合体的公司,这家公司旗下的贝尔实验室是现代计算机科技和通信技术的发源地,诞生了晶体管、Unix操作系统、C语言。

AT&T 拥有:

  • 顶级人才:全世界最聪明的人在为它做研究,最有远见在管理它;
  • 领先于时代的技术:无论是基础理论,还是应用技术,不仅领先时代,而且后劲十足;
  • 特别有钱:1994年的营收相当于一个现在中国排名第三的互联网企业的市值(美团); 如此庞然大物,历经数次经济危机,毫发无伤。

AT&T 也并非没有面对过挑战者们,不过所有的挑战者都会被 AT&T 先打价格战消耗继而收购,反垄断法一直想拆分这家公司。

AT&T 是一家先进的股份制企业,我们国家当初很多国企的股份制改革,都是跟人家学的。没有大公司病,管理健康,现金流充沛,很难想象这么牛的公司还会有走下神坛的那天。

AT&T 最大的问题就是它太大了,股东多,最大的股东是:华尔街。每个股东都有各自的想法,最直接的想法就是挣钱。 其实一个企业是没有思想的,有思想的是人,往往最有思想的是创始人。100多年过去了,AT&T 的创始人早已离世,现在是拿着高薪的总裁们在管理这家公司。 就失去了主见,不受某个总裁的意志控制了。这个时候,一些自然规律便开始主导。

  • 资本: 资本关心股票升值;
  • 董事会成员:个人能挣多少钱;

在这样的背景下,资本们想了个能让业绩增长的好主意,于是 AT&T 做了一个决策,这个决策是 AT&T 衰落的关键点:

当时 AT&T 手里有两笔业务,一笔是设备制造,一笔是长话业务,通信设备几乎处于垄断地位,通话业务由于 1984年的反垄断拆分,大概占有 3 分之 1 的份额。

另外两个 MCI 和 Sprint 由于跟AT&T 的竞争关系,就不敢买 AT&T 的设备。于是 AT&T 为了给竞争对手卖设备,把设备和通信分别拆分为两个公司,从事设备制造的朗讯(Lucent)。

和预期一样,业绩得到了大幅增长,资本家和高管手里的股票翻了番,他们也没想到原来拆分这么赚钱的一个骚操作。

于是 AT&T 又拆了一次,这次一分为四,分为长途电话、移动电话、企业服务、宽带。

彻底动摇了根基,后面的结果大家也都知道了。

资本过度干预企业的正常管理,如撤换雅虎董事会。

风口

当站在风口,猪都能上天。

有些时代的机遇来临时,抓住风口的企业。

  • 快速转型的老牌企业:IBM。IBM以机械时代的办公设备起家(打字机,自动制表机),抓住了电子技术的浪潮;

  • 新秀:

    • 苹果:个人计算机,封闭;并缔造了 iPhone(手持计算设备);
    • 英特尔,芯片;
    • 微软,操作系统,并和 IMB 合作;
    • 雅虎:互联网,免费模式;没有免费,就谈不上互联网。
    • Google: 互联网的搜索;移动操作系统(Android);
    • 华为:抓住了 5G

这是属于时代的红利,每个领域必然都会出一个霸主;

为什么是他们?

  1. 不受华尔街控制。
    • Google 尽可能晚的上市;
    • 尽可能把股票卖给散户;
    • 分为多次融资,确保股权尽可少的被稀释,同样比例的股份出让,融资金额却高得多;
    • A/B 股权架构,同股不同权;如刘强东依然对京东有 80% 的投票权。

同样是搜索,为什么百度就要迎合资本市场。

  1. 人才理念
    • 创业期:以一敌百,尽可能用优秀的人才,注意:人才在这样的时期下的公司里也是会快速成长的。所以伴随着创业阶段成长起来的人才的价值会超过外聘高管;
    • 成长期:工程化,让初级工程师或者普通员工也能很好的产出;
    • 对项目负责人充分授权,这样可以加速决策,提升效率;臭鼬公司(几乎引领了现代飞行器的研发)14条原则里的第一条。
  2. 创始人的理念能得到贯彻,如Google的三个创始人理念很一致。

4. 遵循商业本质:

  • 以更低的价格把更好的东西生产出来,并卖出去(自然也不愁卖)。苹果和微软做的事情,就是把只有企业买的起的小型机,变成 PC(个人计算机),走入平常百姓家。

库克为什么供应链管理出身,能够掌舵苹果。因为供应链管理的价值就是:

  • 降低生产成本;
  • 保障产品质量。比如大疆就因为供应链腐败问题导致过产品质量问题(以次充好),一度成为危机,大疆于是铁腕反腐。
  • 供应链安全:苹果不会把鸡蛋放在一个篮子里,每个元件的供应商都有备胎。

  • 用户体验,做对用户有价值的事情(如降低用户使用成本);百度和 Google 的在搜索引擎上的商业模式几乎一摸一样,Google 通过技术手段能够让广告只在 具有高点击转化可能性的时候出现,这套算法不仅比百度挣得多,还保留了最佳的用户体验。

  • 门槛,生态 物美价廉和用户体验,让产品得以被用户接纳,如果要让企业保持持续的竞争力,最重要的就是门槛和生态了。 当微软的比尔盖茨苹果在推出第一代图形界面 mac 的时候,一下子就傻眼了,dos 系统还像是上个时代的产品。 于是微软决定重新站在风口之上,开始开发 windows 系统,这一开发就开发了整整 9 年。 对于不知道几万亿级的个人计算机市场,苹果一家公司显然是满足不了的,加上乔布斯正在跟他招来的ceo在干架,这些年也诞生了不少生产和组装计算机的公司,IMB,惠普,而他们都有一个共同的特点,就是缺乏好的操作系统。

微软就是这个生态市场中的主导者,微软做了这么几件事情:

  1. 放任盗版,扩大市场占有率;
  2. 为应用开发者尽可能提供便利;
  3. 兼容,为了兼容微软都造了子系统,现在 windows10 上依然可以运行XP的应用;

一波操作下来,windows操作系统上的游戏,office软件已经非常成熟了。后来的 Android 系统几乎照搬了微软的策略。只是把放任盗版变成了开源。

生产关系

上层建筑决定下层建筑,生产关系决定了生产力。

Google 与华尔街的博弈,则是尽可能让公司远离短视资本的控制。

少年时期,学社会主义,马克思思想,总觉得这是国家洗脑的玩意。但是现在重新审视这些经典理论,才渐渐领略其奥义:

生产关系的目标是提升生产力,反推就是说,如何将生产力最大化,然后制定相应管理政策。

如 Google 提出的“三七理论”,让员工有30%的时间可以干自己喜欢的事情,以此保障企业的创新能力,这也是 Google 多元化扩张遍地开花的基础,如前端框架 Angular 则是在这样的背景下孵化出来的。

曾经有人质疑,咱们国家是披着社会主义皮的资本主义国家,其实在这个原则下,是姓资还是姓社,并不重要。主要的是管理体制,要能够持续推动社会生产处于高效率状态。

北欧国家被认为是高福利国家,最接近社会主义。年轻人不必为买房买车发愁,在这样的条件下,催生了大量富有创新性的事情。

如 Rails(Web 开发框架的鼻祖,几乎所有的语言都有开发类 Rails 的框架) 框架的作者 DHH 在瑞典,谈及开发 Rails 框架的时候,就感谢了国家的高福利让其能够有精力做这些短期不会产生效益的事情。

急功近利,这个也是我们国家目前的社会问题。

资本主义社会是为有产阶级服务(有钱)的,所以在资本主义社会,钱生钱。但是处于自然规则下的钱是短视的。

试想,如果中国的华为是上市企业的话,还可能成长到现在的体量,拥有如此顽强的生命力么,美国举国之力都没干掉华为。

贯彻生产关系,知行合一

也就是我们前面提的,就算创始人构建了好的框架,但是创始人的理念如果得不到贯彻执行,也是枉然。

企业创始人需要因为志同道合走到一起。因为理念不合,利益分配,最后失败的创业企业太多了。

为什么有个阶段,我们会觉得社会主义是国家给我们洗脑的玩意呢。就是因为在那个阶段,我们看到的和书本上学到的不一样。

  • 腐败;
  • 有钱人的规则;
  • 西方世界的先进;

但是现在国家在治理能力上的提升,也是不断践行党的纲领,践行社会主义理论的情况下,我们看到了生产关系的魔力;

  • 民主集中制:管理效率与管理风险的平衡。项目责任人负责制毋容置疑是效率最高的方案,但是前提是项目责任人不出问题。这就需要手段对可能出现的风险进行控制。

  • 集体所有制:比如国家电网,电信企业,让我们全境通了电,覆盖了4g信号。比如国家邮政,让中国所有的地方都有物流。比如我们一个最穷的省的高速公路覆盖超过了印度整个国家。

信息的流动和物品的流动效率是整个社会发展的本质,如果没有集体所有制。这些基础是不可能有的。美国这么发达的国家,很多山区都是没有手机信号的。

马克思对社会发展阶段的论述,从封建社会到资本主义,从资本主义再到社会主义,是有其自然的发展规律的。

这些发展规律的底层是社会的总生产力。

当老百姓饭都吃不饱的时候,他们想的是怎么能够让明天有顿吃的。当青年人买不起房的时候,他们想的是怎么今年赚十几二十万。

当不愁吃穿住的时候,当手里的钱这辈子都花不完的时候,才会开始想 祖国和社会的明天。

社会财富会影响整个社会的价值观。我们现在的价值观可能还有点跟不上社会财富,我们现在应该考虑的是更多的未来。国家给了我们这么好的4g网络,我们不应该刷抖音和打游戏。

当然如果有年轻人在房子还买不起的时候,依然在想祖国的明天,这样的年轻人是值得敬佩的。

比如我们,笑~

工程师的产品观

我常常对我曾供职的两个公司感到遗憾,因为我觉得这两个公司发给我的工资完全就没有赚回来嘛。A公司,参与开发的一个项目不了了之;B公司,负责开发的项目上线三个月之后公司便停止维护。

如果产品决策失误,作为程序员,是不是就写了一套没有多大价值的代码。谁都不希望自己参与的项目夭折,虽然多少有些身不由己,但我还是希望自己仍然能够生产出更多的价值。

也许有人认为“拿钱干活”就好了,何必要关心产品层面的事物。从某种意义上来说,我认为程序员只是一个生产力工具,生产工具的价值在于其所生产的产品,好的生产工具也会为了产品而优化自身,所以我们程序员应该具有带着对产品的思考去思考代码,让自己成为更优秀的生产力工具。

提炼(Design)

  • 产品观:提炼需求
  • 程序员:模型与架构设计

一个软件应用替代的是现实中的服务的一个或多个环节,我们所参与构建的大多数应用是将现实中的服务转变为机器(屏幕)提供给人的服务。无论现实还是应用软件,都需要满足人们的具体需求。产品经理需要提炼现实中的需求,设计产品的功能和交互,从而能够满足相应的需求。

对于程序员来说,有两件事最头疼,一件事是起名字,另一件事则是设计模型。模型设计是程序的基础,每个模型应该包含哪些属性,各个模型之间有什么样的关联或关系。如果能设计合理,符合事物的本质,就能够具备良好的扩展性。相信不少程序员在开发的过程中都或多或少有过一些纠结:这个模型当初为什么要这么设计。

之前我参与过一个项目,需要配合食物库开发一套电商系统。与传统的电商系统有区别的是,食物库中只有一部分是会销售的。如果这个食物没有销售,那么它就仅仅只是一个食物,只有当它可以被销售的时候,这个食物才是商品。也就是说商品所具有的属性是由销售所产生的,比如价格、库存等。于是我们重新设计了一个商品的模型,这个模型不包含任何食物本身的信息,而是仅包含作为交易物品所具有的属性,然后围绕商品为中心,设计了供应商,交易等一系列模型。 当这个开发工作结束的时候,我惊奇的发现,这套电商系统很容易通过改造让现存的系统具备电商功能。这就是由良好模型设计让代码所具备的灵活扩展的能力。

通用(Common)

  • 产品观:用户体验的一致性
  • 程序员:代码复用

在应用程序的人机界面中,良好的用户交互需要具备一个重要原则:一致性。UI的一致性可以降低用户的学习成本,让用户觉得系统更容易上手和使用。比如统一的菜单样式,统一的视觉,统一的输入处理。这些需要保持一致的模块,体现在我们程序员的工作中,是一定可以将其作为一个可以通用的模块进行coding的。

大家都知道我们写代码有一个重要原则:DRY(Don’t Repeat Yourself),为了达到这个目标,我们需要知道那些功能是可以通用的,该通用性功能剥离出来的代码,就可以形成通用的模块。

我曾经供职的一个公司,产品线颇多,每个小项目由不同的产品经理负责,没有统一的UI,每个新项目都是独立的设计师完成设计,我们开发的时候就只能按着设计师提供的设计稿还原界面。

如果可以从品牌层面统一UI风格,统一交互形式,我们程序员也就可以更容易提炼出一套通用的代码。这些通用的设计和代码其实都是一个企业的积累,如果没有这些积累,不论这个企业做了多久,它做的每一个产品都仍然像是一个创业企业从零开始做的产品。最终我们为了能够写出更通用的代码,我们也促成了公司在产品层面的一致性设计,这也算是程序员推动企业产品进步的一个案例。

取舍(Better Choice)

  • 产品观:功能的取舍
  • 程序员:代码的取舍

取舍的问题,是成本与收益的问题。优秀的产品往往会聚焦到核心功能上,然后围绕核心功能展开设计,我们见过太多想做成大杂烩最终做成了四不像的失败产品,这些产品失败就败在取舍。将有限的资源应用到最值得投入的地方,才能产生最大的收益。

我们程序员的时间就是宝贵资源,对于我们来说,产品在功能上的侧重和取舍,也是我们在代码层面的取舍。有些临时的功能,非核心的功能就可以不必过分追求代码质量。同样的写测试,并没有必要100%测试覆盖,而是覆盖核心功能,经常被调用的方法,经常被使用的模块就需要最佳实践良好设计。

我们做项目,偶尔会遇到时间紧任务重的时候,客户对某个功能实现的期望比较急切。我现在所参与的项目,需要实现一个报表功能,与我们一起合作的有一个来自歌舞之乡印度的程序员,这位同仁为这个报表系统的前端开发引入了最新的JS框架React,因此这个报表做了大概一个月左右。当我接手的时候,还需要实现剩余6个报表功能的UI,我并没有贪恋新技术,而是采用成熟的方案去实现,仅用了不到2天时间去实现了所有剩下的UI功能,效果也一样。我觉得这种取舍是有必要的(当然不排除印度哥们是为了练技术)。

作为一名工程师,我们是帮客户实现一个应用产品,而不是去完成客户的指令。所以从产品层面去思考我们的工作,我觉得还蛮有意义的。

结语

从业几年来,经手的完整项目有了一些了,电商、社交、学习系统、医疗系统等都有所涉及。工作越久,就发现所参与开发的各套系统之间通用的部分越来越多。我最大的收获就是通过深入分析每一个功能,将其分离组合成一些通用的模块。模块化及复用代码是快速制造,保证质量,降低成本最有效的策略。我甚至有一个大胆的设想,在自己数年的工作之后,我会积累出足够数量的“积木项目”,当有新项目的时候,就从自己的“积木库”里挑出来组合一下。然后就去喝喝茶,坐等客户结账。

前后端分离

什么是前后端分离

要谈“前后端分离”这个话题,首先聊聊什么是“前后端分离”?常规意义上的“前后端分离”更多是一种软件工程上的概念,主要具备以下特点:

  • 前端和后端属于独立运行的两个应用(Application);
  • 前端和后端通过网络通信交换数据;

“前后端分离”的意义主要有:

  • 前端和后端的开发工作可以并行,彼此的进度相互不影响;
  • 由于前端和后端往往采用不同的语言和技术框架,程序员可以更专注于一个更小范围的技术领域。在 B/S 架构下的应用,前端往往使用的是 HTML/CSS/Javascript 技术,后端则使用 Ruby/PHP/Java(Spring框架) 等技术。

在 C/S(Client/Server) 架构下,Client 端属于“前端”,Server 端属于“后端”,本身就是前后端分离了。所以“前后端分离”主要是针对 B/S(Brower/Server) 架构的软件工程,即运行在浏览器里的 Web 应用。在没有“前后端分离”之前,传统的 web 应用往往是由 Server 端生成能够在浏览器里运行的 HTML 代码,HTML 代码里亦包含了数据。

前后端分离的缺点

“前后端”分离固然带来了软件工程上的便利,同时也带来了新的成本和新的问题。

在传统的“前后端”一体化应用中,由 Server 端应用直接生产 HTML,采用“前后端分离”的方案后,后端输出 JSON(目前较普遍使用的方案) 格式的数据,然后前端对 JSON 数据进行解析,然后再渲染出 HTML 页面。在这个过程中,有些在后端已经做过的事情,前端需要再做一遍,包括:

  • 模型。前端往往不会直接将 JSON 数据转化为 HTML,更多是先在 Javascript 中生成对应的 Model,然后由 Model 再生成HTML,这部分 model 往往被称为 View Model。比如 Vue 就会称自己为 MVVM 库,MVVM 即 Model-View-ViewMode。

  • 路由层。“前后端分离”的前端项目,一般也被称为单页应用(SPA,Single Page Application),单页应用免不了需要定义自己的路由,路由规则往往和Server端的路由的重复。

毋容置疑,前后端分离带来的是大量新增的工作量。虽然有一些优秀的前端框架的出现,如 Vue 等极大的方便了前端应用的开发。其实也正是这些前端框架的出现,才出现了“前后端分离”的流行。至于是先有蛋还是先有鸡,已经不重要了。

除了开发成本的增加。前后端分离还有一个巨大的痛点,即协作成本。

Rails下的下一代前后端分离

Webpacker 最佳实践

自从 Rails 6 开始,官方默认采用 webpacker 来处理 Javascript,保留 Assets Pipeline(Sprockets) 作为 CSS、图片等静态资源处理方案。

webpack 的引入显然解决了一些 Assets Pipiline 很难解决的痛点,比如对编写下一代 JS 的支持。

虽然 webpacker 的生态也还在不断完善之中,但是从 assets pipeline 切换到 webpacker 也并非无痛的,最典型的场景就是对 Rails engine 中 assets 自动加载变得很难。

不过我们可以自己动手,丰衣足食,尽可能减少迁移过程的痛苦,在此分享下我的实践。

配置 JS 处理方式

移除 assets pipeline 对 js 处理的配置

app/assets/config/manifest.js 文件默认配置了 assets pipiline 需要处理的静态资源文件,把 js 相关的内容移除;

如果 app/config/application.rb 中定义了 assets.js_compressor,也一并移除;

曾经的 uglifier, coffee-rails 等 gem 也可以永远拜拜了,再也不会为配置 execjs 的 runtime 而烦恼。

修改 webpacker 配置

webpacker 默认配置中,js的路径是 app/javascript/packs,为了兼容老项目,我们将其路径更改为 app/assets/javascripts, 修改 app/config/webpacker.yml 中的如下配置:

source_path: app/assets
source_entry_path: javascripts

兼容 Rails engine

由于我们的Rails项目采用了组件化开发,引入了多个 engine, 大量的 js 代码散布在 engine 的 app/assets/javascripts 目录下。

如何才能让 webpack 在编译的时候能够加载 engine 下的 js 代码呢,webpack 的工作目录是 Rails主项目,关键点就是如何让 webpack 知道各个 engine 在文件系统中的具体位置,也就是 ruby 和 js 之间分享数据。

我采用了一个比较粗暴的办法,在 rails 项目启动完成的时候,将 engine 的位置信息更新到 config/webpacker.yml 文件当中,然后在 config/webpack 的配置文件中去解析这个文件,获取 engine 的路径信息。

1. 在 rails 项目中导出 engine 路径信息

先实现对 webpacker.yml 文件的读写,代码如下:

# https://github.com/work-design/rails_com/blob/master/lib/rails_com/webpacker/yaml_helper.rb

module Webpacker
  class YamlHelper
    
    # uses config/webpacker_template.yml in rails_com engine as default,
    # config/webpacker_template.yml in Rails project will override this.
    def initialize(template: 'config/webpacker_template.yml', export: 'config/webpacker.yml')
      template_path = (Rails.root + template).existence || RailsCom::Engine.root + template
      export_path = Rails.root + export
      
      @yaml = YAML.parse_stream File.read(template_path)
      @content = @yaml.children[0].children[0].children
      @parsed = @yaml.to_ruby[0]
      @io = File.new(export_path, 'w+')
    end
    
    def dump
      @yaml.yaml @io
      @io.fsync
      @io.close
    end
    
    def append(env = 'default', key, value)
      return if Array(@parsed.dig(env, key)).include? value
      env_index = @content.find_index { |i| i.scalar? && i.value == env }

      env_content = @content[env_index + 1].children
      key_index = env_content.find_index { |i| i.scalar? && i.value == key }
      
      value_content = env_content[key_index + 1]
      if value_content.sequence?
        value_content.style = 1  # block style
        value_content.children << Psych::Nodes::Scalar.new(value)
      end

      value_content
    end
    
  end
end

然后在 rails 初始化过程中增加一个回调,如果相应的 engine 下存在 app/assets/javascripts 文件夹,则将这个路径写入到config/webpacker.yml文件。

# https://github.com/work-design/rails_com/blob/master/lib/rails_com/engine.rb#L30
config.after_initialize do |app|
  webpack = Webpacker::YamlHelper.new
  Rails::Engine.subclasses.each do |engine|
    engine.paths['app/assets'].existent_directories.select(&->(i){ i.end_with?('javascripts') }).each do |path|
      webpack.append 'resolved_paths', path
    end
  end
  webpack.dump
end

2. js 通过数据文件 或许相关的路径信息;

// https://github.com/work-design/rails_com/blob/master/package/index.js

const { basename, dirname, join, relative, resolve } = require('path')
const { sync } = require('glob')
const extname = require('path-complete-extname')
const config = require('@rails/webpacker/package/config')

const paths = () => {
  const { extensions } = config
  let glob = extensions.length === 1 ? `**/*${extensions[0]}` : `**/*{${extensions.join(',')}}`
  let result = {}

  config.resolved_paths.forEach((rootPath) => {
    const ab_paths = sync(join(rootPath, glob))

    ab_paths.forEach((path) => {
      const namespace = relative(join(rootPath), dirname(path))
      const name = join(namespace, basename(path, extname(path)))
      result[name] = resolve(path)
    })
  })

  return result
}

module.exports = paths

这里我们导出了所有的 js 文件路径,用于 webpack 的 entry 配置。

3. 暴露 jquery, rails_ujs 等库

一般的项目都使用了这两个js库,为了能够在代码里使用 $('body'), Rails.ajax 这样的代码,我们需要增加一点配置;

# https://github.com/work-design/rails_com/tree/master/package/loaders
module.exports = {
  test: require.resolve('jquery'),
  use: [
    {
      loader: 'expose-loader',
      options: 'jQuery'
    },
    {
      loader: 'expose-loader',
      options: '$'
    }
  ]
}

我没有对 webpack 和 expose-loader 的代码做深入阅读,不过我认为在 import jquery 的时候直接赋值给 windows.$ 也就解决问题了,不知道 expose-loader 是否还有其他的效果,知晓的朋友可以留言告知下。

4. 接下来修改下 config/webpack 下的 配置文件

// config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const { resolve } = require('path')
const paths = require('rails_com')

const jquery = require('rails_com/package/loaders/jquery')
environment.loaders.append('jquery', jquery)

const env = environment.toWebpackConfig()
env.entry = Object.assign(paths(), env.entry)
env.resolve.modules = env.resolve.modules.concat(resolve('node_modules'))

module.exports = env

config/webpacker.yml 中的 resolved_paths 配置了所有存在js文件的路径,用于配置 webpack 的 resolve.modules,告诉webpack 在解析代码时需要搜索哪些路径,与 assets pipeline 的 assets.paths 配置功能一致;

同时也配置了 babel-loader 中的生效路径。

至此,我们就可以直接编译和使用 engine 下所有的js文件了。

改写 assets pipiline 中的 require 语法

//= require channels  // assets pipeline
import 'channels' // webpacker 

其他提示

  1. webpack 相关配置文件是为 nodejs 使用的,所以使用 nodejs 的模块语法:module.exports/require,前端 js 代码会经过 babel 编译,虽然 webpack能理解 CommonJS 等多种模块体系,但是推荐使用 ES6 的 export/import 语法。

  2. 在Rails开发模式下,如果没有启动 webpack-dev-server, rails会将前端代码编译到 public 目录下,此时修改js代码是不能立即生效的。所以推荐在开发js时,同时启动bin/webpack-dev-server

  3. 当新增或删除了 js 文件之后,entry 改变之后,需要重启 bin/webpack-dev-server。

  4. 由于 config/webpacker.yml 会根据项目的实际路径进行更新,建议将其在 git 中忽略。

  5. 更新 Gemfile 配置: gem ‘webpacker’, require: File.exist?(‘config/webpacker.yml’),这样可以杜绝 config/webpacker.yml 未生成时的报错。

  6. config.webpacker.xxx = xx if config.respond_to?(:webpacker) 这个配置主要是解决上述第5条配置的副作用。

Node.js debugger

在软件开发的过程中总免不了 debug,学会正确的debug姿势 能够有效的提高效率,接下来就科普下 Javascript 中的 dedug 姿势。

Node.js inspect

Nodejs 中提供了一个进程外的 dubug 工具,要使用debug的话,通过 inspect 参数启动 nodejs 即可。

假定我们要调试一个js文件

// code/debug.js
let a = 1

在命令行执行命令:

node inspect code/debug.js

会进入到如下环境:

  1
> 2 let a = 1
debug>

可以通过 help 查看帮助信息。

此时断点位于文件开头的第一行代码,由于此时断点处还未执行首行的赋值表达式:let a = 1,执行 next 进入下一行代码,然后执行 repl 可以进入交互式解释环境(Read Eval Print Loop),方便查看变量、执行表达式等调试操作;

此时输入 a 的结果是 1,如果在执行 next 之前进入 repl 环境,输入 a 的结果则是 undefined,注意并非是报 a is not defined的错误。

在 repl 环境下,可以通过输入 .help 查看帮助信息。

要退出 repl 环境回到 debug 环境,执行 Ctrl + C.

Debugger

使用 node inspect 的方式虽然实现了 debug, 但是从第一行代码就开始进入断点显然不够实用,要想随心所欲的设置断点,可以使用debugger 作为标记。

// code/debug.js
let a = 1
debugger
let b = 2

我们在上述js文件中加入debugger,然后再运行 node inspect,断点依然默认停留在第一行代码处,此时执行 cont, 则直接跳转到 debugger 所标记的断点处。

shebang line nodejs script with debug

对于 shebang line 的 node.js 应用,可以在命令前加上 node inspect, 如:

node inspect webpack --config config.js

dev console

在浏览器环境下,debug 可以使用浏览器自带的开发者工具。

在 chrome 浏览器地址栏输入 about:blank,可以得到一个非常纯净的页面,然后右键菜单,选择检查即可打开开发者工具。

点击 source 菜单,就可以在对应的js代码里设置断点并进行调试了。

queryOjbect

Chrome devTools console 控制台上有一个小众的 API 叫 queryObjects(),它可以从原型树上反查所有直接或间接的继承了某个对象的其它对象,比如 queryObjects(Array.prototype)可以拿到所有的数组对象,queryObjects(Object.prototype)则基本上可以拿到页面里的所有对象了(除了继承自Object.create(null)的对象之外)。而且关键是这个 API 会在内存里搜索对象前先进行一次垃圾回收。

参考阅读