分类 默认分类 下的文章

如何使用金字塔结构来写一篇技术文档

技术人员都知道一篇好的技术文档的价值(这里换个表述),但是如何去写好一篇技术文档却一筹莫展。然而,在咨询业的传奇公司麦肯锡中,有一种叫做 ‘金字塔原理' 的写作方式可以快速的组织一篇结构化的文章。那么我们是否可以用 '金字塔原理' 来写一篇技术文档呢?

我们写技术文档的目的

对于软件来说,最重要的两点莫过于速度和质量了。写技术文档到底能不能提高软件研发速度不好衡量,但绝对可以提升软件研发过程的质量。软件的质量反映的是人类程序员的思考深度,而所有人类程序员都有如下两点先天限制: 缓存不足、上下文不同。

人脑的灵活性让我们有很高的创造力,但是在处理大型问题的时候却很难记住所有细节逻辑。所以将发散的思路梳理好并且记录下来这个过程,不仅可以帮助我们做好系统性的设计,还可以在设计阶段排除很多不必要的风险。简单来说就是: “高配 CPU 带小内存,你需要额外的空间来干活” 。

多人协作中,信息的传达效率以及信噪比是非常重要的。举个例子:我们知道在软件研发领域,英语是事实上的标准编程语言,但是在不同的业务场景中 "prod" 这几个字母的意思可能是 ”product“ 、”produce“ 、“producer” 这几个计算机领域常见词之一。文档的另一个核心作用就是: 显式固化只有开发人员才知道的隐性知识无论是缩写还是简写只要团队约定好字符串与实体的映射,那么整体沟通成本将明显下降。关于这一点,埃里克在 领域驱动设计) 这本书中有着精彩的论述,这里就不赘述了。

总的来说,写技术文档可以帮助我们作出更好的设计、显式固化隐性知识,从而提升软件研发的质量。

金字塔原理

我们用一些篇幅说明了文档和软件质量之间的联系,下面我们就来介绍一下金字塔原理。金字塔原理是一种突出重点、逻辑清晰、层次分明的思维方式,也是一种有效的分析问题、得出解决方案的有效工具。

01

作者在书中提供了一种金字塔式的文章组织方式,用来阐述有逻辑关联的事务,而软件正是不同事务变化时逻辑固化在硬盘上的表现,所以我认为用金字塔原理这种方式来写技术文档是再好不过的选择。

关于对金字塔原理的理解,可以选择看完《金字塔原理》的前三章,或者是通过我摘抄的作者对金字塔原理的解释来做初步了解:

  • 对受众(包括读者、听众、观众或学员)来说,最容易理解的顺序是: 先了解主要的、抽象的思想,然后了解次要的、为主要思想提供支持的思想。因为主要思想总是从次要思想概括总结得出,文章中所有思想的理想组织结构也必定是一个金字塔结构一一由一个总的思想统领多组思想。在这种金字塔结构中,思想之间的联系方式可以是纵向的( vertically)ー即任何一个层次上的思想都是对其下面一个层次上思想的总结;也可以是横向的( horizontally)-即多个思想因共同组成同一个逻辑推理过程,而被并列排在一起。
  • 受众的大脑只能逐句理解作者(演讲者、培训讲师)表达的思想。他们会假定一同出现的思想在逻辑上存在某种关系。如果你不预先告诉他们这种逻辑关系,而只是一句一句地表达你的思想,读者就会自动从中寻找共同点, 将你所表达的思想归类组合,以便了解各个组合的意义。

金字塔原理应用到技术文档写作的流程

02

时时刻刻,必须要提醒你自己:读这篇文档的人类是个缓存很小单线程图灵机,爆栈你就破功了,老弟/妹!

前面讲了很多理论性的东西,那么我们如何将理论与实践结合起来呢?下面分享一下我个人写技术文档的结构:

  1. 核心目标:金字塔的尖顶

    这里是金字塔最耀眼的尖顶,只写最核心的目标,比如说:调研某种技术方案、依照某个 PRD 开发产品部分功能。具体行文思路可以参照 《金字塔原理》 第4章— 序言的写法

  2. 方案概要设计: 金字塔的腰部

    这里是整体方案的蓝图,概要的抽象程度一定要很高,在这里必须可以看到全部架构。文字表述不清楚的地方可以多画图来辅助表达:

    1. 建议使用流程图用来表达状态流转
    2. 建议使用时序图用来表达交互顺序
    3. 建议使用C4 用来画系统架构
  3. 详细技术设计:金字塔坚实的基础

    详细设计部分是对上面概要设计的填充。这里是发现问题的黄金时间,一定要做很细致,需要有数据结构和关键逻辑的伪代码。细节设计做好之后,代码也写完三分之一了,剩下的就是写代码、写测试了。

  4. 维护:金字塔墙面砌砖与补漆抛光工艺

    根据我们上面对写文档目的的论述,文档不是一次性设计消耗,我们需要在开发有调整的时候,即时回来调整文档。举个不恰当的例子:汽车建造过程中刹车和油门换了位置,说明书总是要更新的吧。还有需要注意的一点就是: 当前文档只对当前版本负责,新版本不要在旧的文档上直接修改。

    03

这篇文章用了哪些金字塔原理中的方法

  1. 序言 :这里使用了 《第四章: 序言的常见模式》 中,背景-冲突-疑问 的三段模式,来隐晦的告诉读者可以用金字塔原理来写技术文档
  2. 金字塔原理应用的内容部分是用 顺序推理的方式来演示如何写技术文档

04

  1. 这篇文章的整体结构是归纳的方式

05

结语

金字塔原理是一套非常高效的结构化思考、表达工具。除了用来写文档,作者在书中也列举了很多其他应用场景,比如说:如何将 金字塔原理应用在做 PPT 上,如何用金字塔原理来分析问题等等。推荐各位仔细品读。

也来谈谈软件复杂度

最近一直在做框架开发,很多软件设计理论也有了尝试的机会,渐渐的也有些自己的思考。我一直在想一个问题:之前我所学习到的软件设计方式,几乎都是最佳实践类型的指引:你该如何去做。比如:SOLID 原则 告诉了我们遵从这 5 个原则就可以设计出优秀的软件。

那么什么是差的软件呢?是主观上的 bad smell 吗?读完《A Philosophy of software design》之后,我觉的软件的好坏可以由两个维度的 复杂度(complexity) 来描述:

  • 机器维度:空间和时间复杂度,时间和空间复杂度代表了执行同样任务的资源消耗
  • 人类维度:软件复杂度,人去理解、维护软件需要消耗的资源(包括时间,沟通,试错等各种成本)

对于软件复杂度 John Ousterhout 给的定义是:Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system. 学习过程中发现其他几篇博客也对软件复杂度有很精彩的观点:

在衡量软件复杂度的方面,Ousterhout 教授的定义方式是基于时间:

C=\sum_{p}{c_pt_p}

The overall complexity of a system (C) is determined by the complexity of each part p (cp) weighted by the fraction of time developers spend working on that part (tp).

这篇中还介绍了两种基于代码的衡量方式:cyclomatic complexiteyNPath, 我认为这两种在工程中有实践的意义。

复杂度是软件的固有属性之一,只有理解了什么是复杂度和衡量标准才能更好的处理软件中的复杂度。具体如何处理软件复杂度,Ousterhout 教授在他的书中有很多精彩的案例,强烈推荐阅读。

Dash & 超低延时直播的研究

延时的来源

链式传播叠加的延时

  1. 编码和封装:引入延迟和参数配置、质量要求密切相关。某些流媒体协议可能会引入额外的延迟,因为它们只有在完全接收到后才输出一大块(chunk)媒体内容。
  2. 第一英里上传(first mile upload):将打包内容上传到CDN通常会受到商业条款的限制。例如,与来自新闻工作室的租用线路设置相比,如果通过无线连接完成上传将会产生更大的延迟。
  3. CDN传播:为了大规模传送内容,大多数媒体管道都利用内容传送网络(content delivery network)。因此,内容需要在不同缓存之间传播,从而引入额外延迟。
  4. 最后一英里交付(last mile delivery):用户网络连接可能会对延迟产生重大影响。用户可以在家庭网络连接到wifi热点,或者使用移动连接来访问网络内容。此外,由于可能会选取不同远近的CDN端点,用户地理位置也会造成额外延迟。
  5. 播放器缓冲区:视频播放器必须缓冲媒体以确保流畅播放。缓冲区大小通常在媒体规范中定义,但具有一定灵活性。播放缓冲是延迟的主要因素,优化缓冲区配置是常态。

7d8dfc0b722b8df93878853e19232c43.png

fig.1 (延迟来源)

延迟的长短

  • 典型延迟(typical latency)18-45s:如下图所示,在这个区域,我们看到一般都是HLS和MPEG-DASH设置,这两种适用于非时间敏感的线性广播,并且不会与广播公司或社交媒体上的其他观众进行任何交互。
  • 较少延迟(reduced latency)5-18s:通过调整HLS和MPEG-DASH流来减少延迟,减少了segment大小并增加了infrastructure的大小。该方法通常用于直播新闻和体育赛事。
  • 低延迟(low latency)1-5s:低延迟通常被视为每个发布者的目标,因为它允许更多交互式用例。
  • 超低延迟(ultra low latency)200ms-1s:可以实现更好的交互性,感觉接近实时。虽然不适合语音通信或会议,但这种延迟通常对于常见用例而言足够低。
  • 实时通信(real time communication)0-200ms:实时通信对于双向会议和通信等用例至关重要。

142a9a1242f3aa2fe1e5102e3219ed15.png

fig.2 (延迟长短定义)

DASH 和低延时

MPEG-DASH an overview

DASH 是一个类似于 HLS 的分片传输协议(其中一些多轨道,无缝切换之类的特性我们这里暂不讨论),DASH 中的列表文件是 mpd (Media Presentation Description) 。根据 mpd 中的几个时间字段(fig.3),我们可以算出 服务器和播放器直接的端到端延迟,这点很重要(详细算法可以看dash.js中getCurrentLiveLatency方法源码)。

fb8fe91d3dff26d1f7675ead769ed73f.png

fig.3 (DASH 时间模型)

能准确的获取端到端延迟在直播中最重要的意义就是:我们有了控制延迟的基础条件。在上面描述延迟图中(fig.1),第二步到第四步的网络传输抖动是我们无法控制的,但是只要我们知道了延迟的具体时间,就可以通过控制播放器播放进度,来实现快进或者慢放来保持稳定延时(sample)。 在下图(fig.4)中,我们通过播放器设置将延迟控制在了5s整。

96dfb5085b6ced836e6c7e04103e1d55.png

fig.4 (示例)

如何稳定进入5s以内? CMAF!

分块编码

实现低延迟的第一个必需行为是分块编码(chunked encoding)。根据MPEG CMAF标准,CMAF中各个对象的命名如图1所示。chunk是最小的可引用单元,至少包含moof和mdat这两部分。一个或多个chunk以形成fragment,一个或多个fragment形成一个segment。标准CMAF的media segment使用单个moof和mdat编码,如图2所示,mdat包含单个IDR(Instantaneous Decoder Refresh,瞬时解码器刷新)帧,这是每个segment开始传输所必需的。
b3a74ef9f53c4050ea245c6c167c2117.png

359bd2896cd7f9c61b115d42da3de80f.png
一般来说,segment将保持一系列chunk,即多个moof / mdat元组的序列,如图2所示。只有第一个元组保持IDR帧。将segment分成这些较短片段的优点是编码器可以在编码后立即输出每个chunk以便传输,这样就会导致整体延迟直接减少相同的量。每个块中包含多少帧没有固定的规定,目前的编码器范围为1至15帧。

DASH DASH-CMFA

再次强调一下,只有满足以下所有条件,才能稳定实现ULL-CMAF的减少延迟功能:

  1. CMAF段中的内容是块编码的。
  2. 编码器调整其DASH manifest/ HLS playlist以适应分块编码的使用和数据的早期可用性。
  3. 编码器使用HTTP 1.1块编码传输将内容推送到origin处。
  4. CDN在分发链的每个步骤使用HTTP块编码传输。
  5. 客户端:

    1. 准确地对segment的请求进行计时,并在live edge的一个segment持续时间内请求该切片;
    2. 在接收到比特流时对其进行解码,并且不用等到segment传输结束。在浏览器中运行的HTML5播放器必须使用Fetch而不是XHR API,因为Fetch允许在数据仍在下载时读取响应主体;
    3. 有一个估计吞吐量的方案,因为标准的segment定时技术将会失效;
    4. 具有缓冲和自适应逻辑以应对非常低的缓冲;
    5. 由于吞吐量波动,如果它落后于直播流,要具有赶上直播流的功能。

参考内容

优化延迟的最佳解决方案(一)
优化延迟的最佳解决方案(二)
优化延迟的最佳解决方案(三)

The importance of low latency in video streaming
视频传输延迟分析及解决方案:CMAF、LHLS

BEST PRACTICES FOR ULTRA-LOW LATENCY STREAMING USING CHUNKED-ENCODED AND CHUNK-TRANSFERRED CMAF
超低延迟CMAF流媒体方案解析

关于加解密想到的

能比改别人写的BUG更恶心的事情,大概只有调别人写的接口了吧

接口调用的时候,为了保障敏感数据安全,一般都会做加密或者签名。md5,sha1,sha256,hmac,各种签名算法,128,256,512,cbc,cfb,AES下茫茫多的加密方式,跨语言实现中各种实现方式,简直叫人焦头烂额。

帮别人踩坑

之前做数据加解密传输基本都在自己的项目中,不同组件虽然是不同语言实现的,但是实现细节自己比较清楚,没有遇到什么问题。最近第一次碰到加解密的坑是一个只会py的小胖子,他要去接别人的API,对方用php实现了一套签名算法 hash_hmac('sha256')。他用py的hashlib.sha256自己实现了一套hashHmac,哈希结果死活不对,网上搜也基本都是这样实现,我帮小胖找了好久,才发现要使用base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest())

自己踩坑

上一次是接华为云市场的时候,对方在文档中只说了要用AES-CBC-128或者AES-CBC-256来进行加解密,但是我这边用php无论如何都解密不了。无奈之下,只好去看官网demo,结果java 的demo 还是一个代码片段,然我这种没环境,没IDE的人,连调试都做不到。实在没办法,只好人脑编译,然后我看到了这几行

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");

secureRandom.setSeed(keyBytes);

keyGenerator.init(encryptType, secureRandom);

SecretKey key = keyGenerator.generateKey();

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));

我的天呦,原来是Key做了一次哈希再拿去运算的。。。

帮别人写代码

是的,就是帮别人写代码。

客户要对接自己的短信通知系统,需要我们把通知内容下发给他们。对接的小哥有点搞不定,一定要我们给他一个demo,c#的,我从来没写过,我们组也没人写过,我们公司也没人写过。

本着原理相同,工具不同的态度,我决定给小哥撸一个demo。不得不说,在陌生的领域里,举步维艰。算法,demo什么的网上找到了很多,但是一直没有可以运行的环境。vs下了一下午都没好,网页工具又非常不给力,调试到怀疑人生。下好vs后,网上的demo一直报错说CFB方式不支持,怀疑人生。晚上和同组小哥讨论的时候发现:只要在vs上创建 .net工程,不要创建.net core工程就不会报错。。。 微软爸爸你真棒! 弄好环境之后,二十分搞定。

一些牢骚

可能是我对其他语言了解不够深入,也有可能有些误解。但是,从易用性这点上来说,PHP真的是世界上最好的语言。我看到有好几个人用多种语言实现了AES的某一个算法来做对比,PHP版本的总是最简洁的。简洁不仅是指代码行数少,更是对使用者和阅读者心智的解放。

可以从使用加密或者签名的意图出发去想一下,我为什么要这么做?我只是想要保障数据安全!不要和我说经过多少轮的变换,能抵抗多么大规模的暴力破解。我只需要知道是用 CFB 还是 CBC 安全,我要用256还是512。只要告诉我AES-128-CBC,我只想用一个函数,一行去解决这个事情。我不想知道你的key是经过md5,还是sha256之类的鬼方式计算的。也不想知道iv是固定的还是放在密文前面还是后面,是都base64还是部分base64。我只想decrypt(method,cipher)

MySQL unique and Laravel soft delete

A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. For all engines, a UNIQUE index permits multiple NULL values for columns that can contain NULL.

laravel 的 soft delete trait 使用 deleted_at 来作为约束。由于需要同时使用唯一和软删除两个特性,所以把 deleted_at 也作为 unique 的一个联合键。测试的时候发现,当 deleted_at 为 null 的时候,可以无限插入,unique并没有约束成功。MySQL官网描述如上,解决方案也容易,直接将 deleted_at 设置为 not null 即可