由一个小问题说一说解决问题的思维


写在前面的话

我这个人其实不太爱写博客。

上大学的时候,学习方法就是看书。先看教科书,教科书看完了,就去图书馆看书。当然,都是要边看边动手的。

书看的没劲了,就到网上找视频教程,生动形象。

后来安装的开源软件多了,就从软件中知道了软件的官网。进一步通过官方文档来学习。

书本、视频、官方文档,这些资料都有一个共同点:成体系又够权威。

不像博客,比较琐碎。

大部分的技术博客有以下问题:

  1. 只是记录一个问题的其中一个原因,以及对应的解决方法;
  2. 甚至是一个错误的解决方法,只是凑巧问题消失了;
  3. 再或者因为版本的问题,随着版本升级,方法已经不再奏效。

点居多,面太少,既不成体系,又不能保证正确性。

如果要把博客写得系统,就跟翻译没啥区别了,工程浩大,不如直接写书。面向问题呢,又如上面所说,分析的不全面,容易浪费读者时间,我也不愿为之。

但是只看不写也不是个好习惯。放在肚子里谁知道我会呢?不往外分享又怎么帮助人呢?

看的书多了,学习的内容多了。我逐渐相信:解决问题的思维比答案本身更重要,并深有感触。

所以我给我的博客的定位是多讲思维,多讲道理。

单纯地讲道理,或者以点带面地讲道理。

这类文章,我放在 明哥讲道 分类中。

下面先来说说这次我遇到的问题。

TOC是什么?

TOC 是 Table of Contents 的简称。翻译为中文就是目录。

目录大家肯定都知道,但是 TOC 就显得比较“高大上”,或者叫”难理解“。

这里给大家的一个建议,对于有明确含义的英文概念,一定要转换成中文去理解再记忆。这样在我们的思维中,整个信息的含义是自成一体的,不需要在脑海中去额外调用英文知识相关的区域再做一次翻译动作。

我在博文《新年新形象,网站换了新主题!》 中提到新主题的特点之一:

5.侧边栏显示文章目录结构。同样是一个很惊喜的功能。

这次说的问题就和这个功能有关。

最初遇到的问题

刚换为这个主题时,我就遇到了一个问题:文章页侧边栏目录处内容为空。

最初,发现这个问题后,我立刻打开主题 Demo 网站,使用浏览器开发者工具查看文章页内容的标题,发现是使用的 h1 标签。而我之前使用的主题,h1 字体非常大,所以我一般不用 h1,从 h2 用起。按一般逻辑,我猜测是目录仅支持 h1 大标题,毕竟侧边栏空间有限。于是我对自己的文章内容做了修改,但本地预览后发现问题依旧。

如果让我来实现显示 Markdown 目录,正常来讲,应该是通过读取各个标题来实现。 h1 标题(即 #)都不能显示,可以肯定这个主题并不是按这个路数实现。

一猜不中。

为了快速确定是否为文章内容的问题,根据控制变量法,我把 Demo 仓库里的博文 md 文件直接放到我的仓库里。本地预览发现可以显示目录内容!

可以确定,就是文章内容问题了!

查看 Demo 仓库的源码,可以很明显注意到在文章开头有以下内容:

* TOC
{:toc}

将这段代码加到我自己的文章开头,本地预览,侧边栏里的目录有内容了!

探寻实现机制

我知道这部分代码可以用来生成 Markdown 目录。

但是博客的文章页面,内容前面并没有目录啊!目录是在页面右上角的!

此时,脑中冒出了一些问题和猜测:

  1. 文章前的目录内容为什么没有显示?
  2. 右上角的目录内容和文章里的目录内容有什么关系?为什么加上代码,侧边栏里的目录就有内容了。难道是文章前的目录内容“跑”到右边去了?

首先探寻第一个问题,这个问题又可以分为两种情况:

  1. Jekyll 生成静态文件时并没有生成目录内容。直观看是这样的。
  2. Jekyll 生成静态文件时生成了目录内容,但是并隐藏了。

这个问题很好确定,打开开发者工具看一下就知道了。

浏览器开发者工具截图

可以看到,生成的 HTML 源码中有 markdown-toc 的内容,但是在 post.css 中,将这部分内容设置为了不显示。即上面所说的第二种情况。

第一个问题的答案确定了,第二个问题中我的猜测也八九不离十了。这样的设计,明显是为了“偷懒”:先利用已有的功能把目录内容生成,再把生成的内容复制一份显示再侧边栏,最后再把原始位置的内容隐藏掉!思路相当“鸡贼”,哦不,是巧妙!

看源码来验证一下猜测:

先看模版 /_layouts/post.html 源码:

<div class="content-navigation col-lg-3">
      <div class="shadow-bottom-center" >
        <div class="content-navigation-toc">
            <div class="content-navigation-header">
                <span class="octicon octicon-list-unordered"></span>&nbsp;目录
            </div>
            <div class="content-navigation-list toc"></div>
        </div>
        <div class="content-navigation-tag">
            <div class="content-navigation-header">
                <span class="octicon octicon-list-unordered"></span>&nbsp;标签
            </div>

使用浏览器开发者工具可以看到 <div class="content-navigation-list toc"></div> 下面被填充了内容,并且和文章正文中被隐藏的目录内容一模一样。

这段代码上下没有相关代码,整个文件中也没有相关的 JavaScript 脚本,说明复制动作不是在这里定义的。

再看对应的 /static/js/post.js 源码,可以很容易看到以下内容:

/**
 * 侧边目录
 */
function generateContent() {
    var $mt = $('.toc');
    var toc = $(".post ul#markdown-toc").clone().get(0);
    $mt.each(function(i,o){
        $(o).html(toc);
    });
}

其中的 clone() 是那么地显眼。再看 var $mt = $('.toc'); 这句,.toc 正是获取上面提到语句定义的类名。最后则是将文章正文的目录内容放置到侧边栏中。

实锤!

解决方法

理清了前因后果,百分百确定了原因。上面提到的解决方法也能确定的确是对症下药。即在所有文章的开头添加代码:

* TOC
{:toc}

遇到新问题

我还没有高兴多久, 又发现一个新问题:首页显示文章列表,文章摘要中会显示目录内容。类似下面这样:

首页截图

多出了红色圈起来的地方,这可不是我想要的结果,太明显了。

并没有注意到主题 Demo 有这个问题,于是去看了下,发现也有这个问题……

只不过作者使用 JekyllLiquid Filters 表达式去除了 HTML 标签,使得摘要是以纯文本的形式显示的,多出来的目录内容显得不明显。

Demo 首页截图

红线部分,就是多出来的目录内容。

改进解决方法

Liquid 过滤器可以去除 HTML 标签,可以去除空行,那有没有一个过滤器能去除前面几行的内容呢?

我查阅了 Liquid Filters 的官方文档 https://shopify.github.io/liquid/filters/abs/,尝试了几个有那么点可能性的过滤器,可惜都不行。

抛开问题,退一步,梳理整个流程:

  1. 使用代码在文章正文生成目录内容;
  2. 隐藏正文目录内容;
  3. 将正文目录内容显示到侧边栏目录处;
  4. 首页文章摘要截取文章前面部分内容,导致显示了正文前的目录内容。

1-3 都是必须的,这样侧边栏才能显示目录内容,这个功能我很喜欢,绝不能作废。

现在的问题就是,怎么解决 4。

post 中可以在头部单独添加 excerpt 字段来设置摘要内容,但是这种方式缺陷也很明显:

  1. 一般只能写一些简短的文字,类似微信公众号的文章摘要;
  2. 我也不想单独为每篇文章写摘要,截取文章前面一段就挺好的。

此时我想到。既然摘要是截取文章前面一段,那我把目录内容放摘要后面不是就可以了?反正正文里的目录内容被隐藏了,放在前面还是后面都是一样的。

尝试修改了一篇文章,果然可以:

  1. 首页摘要不再显示目录内容;
  2. 文章页正文也不显示目录内容;
  3. 文章页侧边栏目录内容正常显示。

所以解决办法就是:

<!--以上为摘要内容-->
* TOC
{:toc}

其中第一行为我在 _config.yml 中配置的摘要分割符:

excerpt_separator: <!--以上为摘要内容-->

又有新问题

这个问题本已告一段落。

没想到,过了几天,我偶尔用手机访问博客,发现又有新问题:在手机上访问时,目录内容会显示在放置 TOC 代码的位置。

也就是说穿插在正文中间,如下图所示:

目录内容显示在正文中间

体验实在是太不友好了。我作为一个重视用户体验的人,绝对不能接受。

再探实现机制

难道主题作者又换了路数?又“偷懒”,移动设备下直接用生成的正文里的目录内容,而不是使用响应式设计把侧边栏目录的内容移到正文前显示?

调出浏览器开发者工具一看,果然是:

@media screen and (max-width: 1199px)
.content-navigation {
    display: none!important;
}

当屏幕分辨率宽度小于 1199px 时,侧边栏直接被隐藏。

再回过头来看正文中的目录内容的 CSS:

@media screen and (min-width: 1200px)
.post ul#markdown-toc {
    display: none!important;
}

当屏幕分辨率宽度大于 1200px 时,不显示。换言之,当屏幕分辨率宽度小于 1200px 时,显示。

这也是响应式设计,作者的实现还是很巧妙。

再梳理整个流程:

  1. 使用代码在文章正文生成目录内容;
  2. 使用高分辨率设备时,将正文目录内容复制显示到侧边栏目录处,并隐藏正文目录内容;
  3. 使用低分辨率的移动设备时,隐藏侧边栏,正文目录内容不做处理,即直接显示。
  4. 将 TOC 代码放在摘要分隔符后面,首页摘要内容不含目录内容,高分辨率设备访问文章页也正常,但是使用低分辨率的移动设备访问时,目录内容的位置不正确。

最终解决方法

之前为了首页摘要能不显示目录内容,把 TOC 代码放后面了。现在想要移动设备的体验好,看来还必须放前面。

要让一个东西有不存在的效果,既删不掉,又挪不走。那就只剩一条路:让人看不到。

之前使用 Liquid Filters 表达式行不通,也考虑过不显示,但是发现首页生成的目录内容 HTML 片段和 文章页是一样的,因为是同样的 TOC 代码生成的嘛。

首页使用的是 style.css 来设置样式,这个是全局的,其他页面也引入来这个样式表,如果把首页的目录内容隐藏了,那文章页侧边栏的目录内容也会被隐藏。同时另一条“挪走”的路可行,完全满足要求,就没有再研究。

现在加上移动设备的显示问题,只能看有没有可能写样式将目录内容隐藏掉。

直接隐藏目录内容会导致文章页侧边栏的目录内容也被隐藏,那就也只有一条路:扩大范围来定位。

经过对比,最终找到了不同之处。

看正文中的目录内容的 CSS:

再来看一下高分辨率下隐藏正文中的目录内容的 CSS:

@media screen and (min-width: 1200px)
.post ul#markdown-toc {
    display: none!important;
}

我最后在 style.css 中添加的 CSS:

section article ul#markdown-toc {
    display: none;
}

写在后面的话

实际上,本文解决的并不是一个很大的问题,最终解决问题要写的代码也很少。如果按传统博客的学法,只要写上问题表现,再附上最后添加的几行代码就行了(本文有四五千字)。

但正如我在文章开头所说,解决问题的思维比答案本身更重要

一个小数点,就可以引发一个大问题。要改正这个问题,也只需要修正一下小数点。然而真正的难点在于:我们怎么知道问题是由小数点的错误引起的?

怎样分析问题,怎样排查问题,看不到的脑中的思维活动,才是解决问题的关键。

我们看到的问题只是表象,内在的错误才是根因。

由表象推内在,这其实就是传统医学的“藏象思维”。

“藏象”即内在之藏与外应之象。这虽然是一个医学词汇,但传统医学的背后则是中华文化与中华哲学,是一种传统中国人看待问题、解决问题的思维方式。

物有物理,事有事理。

人体有人体的运行规则,程序也有程序的运行规则。

在寻找问题的解决方法上,并不一定要懂程序的具体语法。比如隐藏特定 HTML 元素的 CSS 代码具体怎么写,这是一个具体的知识点。但分析出要做隐藏这个事情,其中的思维过程,比如:

要让一个东西有不存在的效果,既删不掉,又挪不走。那就只剩一条路:让人看不到。

这就是一个完全与代码无关的事情。

程序比人体更简单,因为我们还能看到程序的源码,还能调试,这让我们做最后一步,即改正内在错误变得简单。现代医学的长足进步,就在于这一部分,通过解刨学,对人体内部结构有了更深入的认知。

培养思维能力,学习看待问题的独到视角,这是我认为的比知识更重要的事情(当然,知识也是必要的,但只有知识是万万不够的),也是我比较乐于分享的。

以后有机会,会专门写一写“藏象思维”。


 关注微信公众号

DevOps持续交付公众号ID:devopscd