Facilitating Technical Writing Courses

Google 技术写作课程搬运,原文地址:https://developers.google.com/tech-writing/overview?hl=zh-cn Facilitating Technical Writing Courses This section provides resources for anyone facilitating or considering facilitating technical writing courses. The following table contains links to all relevant material for facilitators: for facilitators for students Course Facilitator’s Guide slide deck log pre-class in-class Technical Writing One Facilitator’s Guide[1] slide deck[2] log[3] pre-class[4] in-class[5] Technical Writing Two Facilitator’s Guide[6] slide deck[7] log[8] pre-class[9] in-class[10] If you’d like to facilitate a particular course, please start by reading the course’s Facilitator’s Guide. ...

2020-03-01 · 13 min · 2750 words

Technical Writing Courses

Google 技术写作课程搬运,原文地址:https://developers.google.com/tech-writing/overview?hl=zh-cn Technical Writing Courses Every engineer is also a writer. This collection of courses and learning resources aims to improve your technical documentation. Learn how to plan and author technical documents. You can also learn about the role of technical writers at Google. Overview of technical writing courses The following table summarizes the technical writing courses: Take this course… Title Focus Pre-Class In-Class first Technical Writing One the critical basics of technical writing 2 hours 2 to 2.5 hours second Technical Writing Two intermediate topics in technical writing 1 hour 2 to 2.5 hours The pre-class components introduce topics; the in-class components help students integrate those topics. That said, the pre-class lessons on their own still provide a valuable educational experience. ...

2020-03-01 · 93 min · 19657 words

学习单元测试,告别祈祷式编程

[TOC] 祈祷式编程 祈祷式编程 如果代码中包含以下代码 或者上线后进行这种活动 那么这种编程方式就是祈祷式编程。 用流程图表示基本就是这个样子。 祈祷式编程有什么危害呢? 累,每次写完代码还需要再祈祷 不受控,代码运行结果主要看运气,大仙忙的时候可能保佑不了 解决这个问题有好多种方法,单元测试是其中之一。 单元测试 什么是单元测试 单元测试是由开发人员编写的,用于对软件基本单元进行测试的可执行的程序。 单元(unit)是一个应用程序中最小的课测试部分。(比如一个函数,一个类 google 把测试分成小型测试、中型测试和大型测试。单元测试基本和小型测试的作用类似,但是通常也会使用mock或者stub 的方式模拟外部服务。 理想情况下,单元测试应该是相互独立、可自动化运行的。 目的: 通常用单元测试来验证代码逻辑是否符合预期。完整可靠的单元测试是代码的安全网,可以在代码修改或重构时验证业务逻辑是否正确,提前发现代码错误,减少调试时间。设计良好的单元测试某些情况下可以比文档更能反应出代码的功能和作用。 单元测试这么多优点为什么有人不喜欢写单元测试呢? 单元测试太费时间了,对于编写单元测试不熟练的新手来说,编写单元测试可能比写代码的还费时间 单元测试运行时间太长(这通常是单元测试设计不合理或者代码可测试性较差造成的 祖传代码,看都看不懂怎么写单元测试(这个确实优点棘手。。可以考虑先给新代码加单元测试 不会写单元测试 这篇文章主要关注第四个问题,如何写单元测试。 单元测试的结构 首先看一下单元测试的结构,一个完整的单元测试主要包括Arrange-Act-Assert(3A) 三部分。 Arrange–准备数据 Act–运行代码 Assert–判断结果是否符合预期 比如我们要给下面这段代码(golang)加单元测试: func Add(x, y int) int { return x + y } 单元测试代码如下: import "testing" func TestAdd(t *testing.T) { // arrange 准备数据 x, y := 1, 2 // act 运行 got := Add(x, y) //assert 断言 if got != 3 { t.Errorf("Add() = %v, want %v", got, 3) } } 如何编写好的单元测试 什么样的单元测试才是好的单元测试呢? ...

2019-10-07 · 3 min · 625 words

markdown中code生成图片的实现

前几天写了《markdown 生成头条文章的一个思路》,周末就试了试。 先回顾一下思路,大致流程如下: 这里的三个关键点是: 提取code 把code 转换为html 把html 生成图片 code 替换成图片 第一个很简单,只有用正则表达式就可以解决: _fenced_code_block_re = re.compile(r''' (?:\n+|\A\n?) ^```\s*?([\w+-]+)?\s*?\n # opening fence, $1 = optional lang (.*?) # $2 = code block content ^```[ \t]*\n # closing fence ''', re.M | re.X | re.S) 这个正则来自 python-markdown2: https://github.com/trentm/python-markdown2 这个正则只匹配了 ``` 样式的代码,对于前边有四个空格的并没有做处理(也不想做处理,还是严格一点好)。 第二个也不麻烦,只需要把提取出的code 放到html 中,下面是一个html模板: <html> <head> <link rel="stylesheet" href="http://media.gusibi.mobi/highlight/static/styles/atom-one-dark.css"> <script src="http://media.gusibi.mobi/highlight/static/highlight.site.pack.js"></script> <script>hljs.initHighlightingOnLoad();</script> </head> <body style="width: 640px;"> <pre> <code class="{{.Language}}">{{.Code}}</code> </pre> </body> </html>` 这里有一个点是渲染html 页面的时候, 由于加载html 页面的工具都是get请求,这里我们需要先把code 数据保存起来。所以请求code 的html 页面分成了两步。 ...

2019-06-15 · 2 min · 260 words

markdown中code生成图片的思路

最近在头条上写东西,遇到了一个比较烦的事情—编辑器不支持代码。这对于一个像我这样使用代码凑字数的人来说实在不是一个好的消息。但是等头条改进编辑器太遥远了,只能自己自足实现一个替代方案了–把代码替换成图片。 一段代码的时候,我随手截图,简单完成了; 两段代码的时候,我随手随手截图,也完成了; 三段代码的时候,我随手随手随手截图,强忍着完成了; 等我发现代码越来越多的时候,不能忍了。 懒惰是程序员的美德,不能再花费时间干这些事情了。我觉得要写个程序,把markdown 中的代码自动生成图片。 考虑了一下,大概需要做的工作是: 把markdown 中 “ ” 包换的代码提取出来(也可以使用工具先把markdown 转换成html 再解析html 取出code 把每一段code 分别生成图片 把图片对应的代码替换掉 想想还是很简单的。那就开始吧。 但是到第二步的时候遇到了问题,code 如何生成图片,生成什么样的图片? 首先code 需要保持原有的样式,如果能高亮那就更好了(嗯,高亮 生成图片的时候是把code 作为文字使用PIL(我使用python)写在背景上么,图片大小是多少,高亮怎么实现 算了,还是先把code 生成html,然后截取html页面吧。(这样html 还能使用 highlight.js 来实现高亮) 如何动态生成包含code 的html 页面呢? 如何把截取html 页面呢? 动态生成包含code 的html 页面有两个思路: 使用post 请求,把code 写入数据库(或者文件),然后返回id,再使用id GET 请求获取页面(需要存储,两次请求) 压缩code,把code 作为url参数,使用GET请求获取页面(可能会造成url太长的错误) 那如何截取html呢? 如果是python,可以使用pyqt,渲染html页面,截取webview。 如果使用node,可以使用 html2canvas。 大致流程如下: 哎,这一篇没有代码,就凑不了多少字。 最后,感谢女朋友支持和包容,比❤️ 也可以在公号输入以下关键字获取历史文章:公号&小程序 | 设计模式 | 并发&协程 内推时间

2019-06-13 · 1 min · 59 words

PostgreSQL jsonb 使用入门

json 类型 说明 根据RFC 7159中的说明,JSON 数据类型是用来存储 JSON(JavaScript Object Notation)数据的。这种数据也可以被存储为text,但是 JSON 数据类型的优势在于能强制要求每个被存储的值符合 JSON 规则。也有很多 JSON 相关的函数和操作符可以用于存储在这些数据类型中的数据 PostgreSQL支持两种 JSON 数据类型:json 和 jsonb。它们几乎接受完全相同的值集合作为输入。两者最大的区别是效率。json数据类型存储输入文本的精准拷贝,处理函数必须在每 次执行时必须重新解析该数据。而jsonb数据被存储在一种分解好的二进制格式中,因为需要做附加的转换,它在输入时要稍慢一些。但是 jsonb在处理时要快很多,因为不需要重新解析。 重点:jsonb支持索引 由于json类型存储的是输入文本的准确拷贝,存储时会空格和JSON 对象内部的键的顺序。如果一个值中的 JSON 对象包含同一个键超过一次,所有的键/值对都会被保留(** 处理函数会把最后的值当作有效值**)。 jsonb不保留空格、不保留对象键的顺序并且不保留重复的对象键。如果在输入中指定了重复的键,只有最后一个值会被保留。 推荐把JSON 数据存储为jsonb 在把文本 JSON 输入转换成jsonb时,JSON的基本类型(RFC 7159 )会被映射到原生的 PostgreSQL类型。因此,jsonb数据有一些次要额外约束。 比如:jsonb将拒绝除 PostgreSQL numeric数据类型范围之外的数字,而json则不会。 JSON 基本类型和相应的PostgreSQL类型 JSON 基本类型 PostgreSQL类型 注释 string text 不允许\u0000,如果数据库编码不是 UTF8,非 ASCII Unicode 转义也是这样 number numeric 不允许NaN 和 infinity值 boolean boolean 只接受小写true和false拼写 null (无) SQL NULL是一个不同的概念 json 输入输出语法 -- 简单标量/基本值 -- 基本值可以是数字、带引号的字符串、true、false或者null SELECT '5'::json; -- 有零个或者更多元素的数组(元素不需要为同一类型) SELECT '[1, 2, "foo", null]'::json; -- 包含键值对的对象 -- 注意对象键必须总是带引号的字符串 SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json; -- 数组和对象可以被任意嵌套 SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json; -- "->" 通过键获得 JSON 对象域 结果为json对象 select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::json->'nickname' as nickname; nickname ------------- "goodspeed" -- "->>" 通过键获得 JSON 对象域 结果为text select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::json->>'nickname' as nickname; nickname ----------- goodspeed -- "->" 通过键获得 JSON 对象域 结果为json对象 select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::jsonb->'nickname' as nickname; nickname ------------- "goodspeed" -- "->>" 通过键获得 JSON 对象域 结果为text select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::jsonb->>'nickname' as nickname; nickname ----------- goodspeed 当一个 JSON 值被输入并且接着不做任何附加处理就输出时, json会输出和输入完全相同的文本,而jsonb 则不会保留语义上没有意义的细节 ...

2019-05-30 · 10 min · 1984 words

创建高质量的代码--软件构建中的设计

《代码大全》读书笔记 太长不看版 软件构建中的设计 软件设计是一项明确的活动 设计中的挑战 软件设计一词意味着去构思、创造或发明一套方案,把一份计算机软件的规格说明书要求转变为可实际运行的软件。 设计就是把需求分析和编码调试连接在一起的活动。 好的高层词设计能够提供一个可以稳妥容纳多个较低层次设计的结构。 设计是一个险恶的问题 险恶(wicked)的问题就是那种只有通过解决或部分解决才能被明确的问题。 Tacoma Narrows 大桥是一个险恶问题的好例子,因为直到这座桥坍塌,工程师才知道不应该只考虑桥的负荷,还需要充分的考虑空气动力学因素(只有建造大桥,才能从中学到需要考虑额外的环节)。 设计是一个了无章法的过程(即使它能得处清爽的成果) 是因为在设计的过程中可能会采用很多错误的步骤,多次出错 因为设计的优劣差异往往非常微妙 因为不能判断设计是否足够好 设计就是确定取舍和调整顺序的过程 现实世界中,设计者工作的一个关键内容就是衡量彼此冲突的各项设计特性,并尽力在其中寻求平衡。响应速度优先和开发时间短优先得出的设计结果可能是不同的。 设计受到诸多限制 设计的要点一部分是在创造可能发生的事情,另一部分是在限制可能发生的事情。 如果一个人有无限空间和资源来建造房子,可能会建造出无法控制的建筑。正是因为有了限制,才得出了简单的结果。软件设计也是一样。 设计是不确定的 每个人设计的结果可能是不同的,并且可能用起来都不错。设计没有标准答案。 设计是一个启发式的过程 设计过程中充满了不确定性,因此设计技术也趋于具有探索性–“经验法则”或者“试试没准能行”–而不是保证能产生预期结果的可重复的过程。 设计是自然而然形成的 设计不是在谁的头脑中直接跳出来的,它是在不断的设计评估、非正式讨论、写试经验以及修改试验代码中演化和完善的。 关键的设计概念 软件的首要技术使命:管理复杂度 本质的难题和偶然的难题 偶然的难题可以理解为bug,编程语言笨拙的语法,等易于发现容易解决的问题。 本质的难题则比较复杂,本质上说,软件开发就是不断去发掘错综复杂,相互关连的整套概念的所有细节。本质困难就是: 要面对复杂、无序的现实世界; 精确而完成的识别出各种依赖关系和外部情况 设计出完全正确而不是大致正确的解决方案 。。。 管理复杂度的重要性 一个失败的项目如果是由于技术原因而失败,通常都是因为软件复杂度失控了。如果复杂度失控,那么软件就会变得极端复杂,没有人知道它能做什么,它出了问题如何解决。 管理复杂度是软件开发中最为重要的技术话题。 在软件架构层次上,可以通过把大的系统分解为多个子系统来降低问题的复杂度,多个简单的问题比一个复杂的大问题更容易理解。 子系统相互间应该减少依赖; 子系统的关注点应该是相互分离的。 如何应对复杂度 高代价、低效率的设计源于下面三种根源: 用复杂的方法解决简单的问题 用简单但错误的方法解决复杂的问题 用不恰当的复杂的方法解决复杂的问题 用下面的方法管理复杂度 把任何人在同一时间需要处理的本质复杂度降到最低 不要让偶然性的复杂度无谓的增长 理想的设计特征 最小的复杂度 易于维护 松散耦合 可扩展性 可重用性 高扇入:让大量的类使用某个给定的类。(意味着设计出的系统很好的利用了在较低层次上的工具类 低扇出:让一个类少量或始终的使用其他类。(高扇出(7个)意味着一个类过多的使用了其他类,可能会变得过于复杂 可移植性 精简性:没有多余的部分 层次性:比如一个新系统会用到很多设计不佳的旧系统,这时就应该为新系统编写一个负责同就代码交互的层(代理模式) 层次性能把低劣的代码紧闭起来 如果能最终抛弃或重构旧代码,旧不必修改处交互层之外的任何新代码。 标准技术:用到的外来的、古怪的东西越多,也越难理解。 设计的层次 1. 软件系统(Software System) 2. 分解为子系统或包(Division into Subsystems or Packages) 这一层的主要目的是确定如何把程序分为主要的子系统,并定义清楚允许各子系统如何使用其他子系统。 ...

2019-05-29 · 2 min · 305 words

JWT RefreshToken 实践

Json web token (JWT), 根据官网的定义,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 详细介绍可以查看这篇文章 理解JWT(JSON Web Token)认证及实践 JWT 特点 优点 体积小,因而传输速度快 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输 严格的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为你的应用而定制化。 支持跨域验证,可以应用于单点登录。 存在的问题 JWT 自身(在 payload 中)就包含了所有与用户相关的验证消息,所以通常情况下不需要保存。这种设计存在几个问题: Token不能撤销–客户端重置密码后之前的JWT依然可以使用(JWT 并没有过期或者失效 不支持refresh token,JWT过期后需要执行登录授权的完整流程 无法知道用户签发了几个JWT 针对第一个问题,可能的解决方法有: 保存JWT到数据库(或Redis),这样可以针对每个JWT单独校验 在重置密码等需要作废之前全部JWT时,把操作时间点记录到数据库(或Redis),校验JWT时同时判断此JWT创建之后有没有过重置密码等类似操作,如果有校验不通过 当然,这种解决方法都会多一次数据库请求,JWT自身可校验的优势会有所减少,同时也会影响认证效率。 这篇文章主要介绍解决第二个问题(不支持refresh token)的思路。 refresh token refresh token是OAuth2 认证中的一个概念,和OAuth2 的access token 一起生成,表示更新令牌,过期所需时间比access toen 要长,可以用来获取下一次的access token。 如果JWT 需要添加 refresh token支持,refresh token需要满足的条件有一下几项: 和JWT一起生成返回给客户端 有实效时间,有效时间比JWT要长 只能用来换取下一次JWT,不能用于访问认证 不能重复使用(可选) refresh token 获取流程 refresh token 使用流程 代码示例 import jwt import time # 使用 sanic 作为restful api 框架 def create_token(account_id, username): payload = { "iss": "gusibi.mobi", "iat": int(time.time()), "exp": int(time.time()) + 86400 * 7, "aud": "www.gusibi.mobi", "sub": account_id, "username": username, "scopes": ['open'] } token = jwt.encode(payload, 'secret', algorithm='HS256') payload['grant_type'] = "refresh" refresh_token = jwt.encode(payload, 'secret', algorithm='HS256') return True, { 'access_token': token, 'account_id': account_id, "refresh_token": refresh_token } # 验证refresh token 出否有效 def verify_refresh_token(token): payload = jwt.decode(token, 'secret', audience='www.gusibi.com', algorithms=['HS256']) # 校验token 是否有效,以及是否是refresh token,验证通过后生成新的token 以及 refresh_token if payload and payload.get('grant_type') == 'refresh': # 如果需要标记此token 已经使用,需要借助redis 或者数据库(推荐redis) return True, payload return False, None # 验证token 是否有效 def verify_bearer_token(token): # 如果在生成token的时候使用了aud参数,那么校验的时候也需要添加此参数 payload = jwt.decode(token, 'secret', audience='www.gusibi.com', algorithms=['HS256']) # 校验token 是否有效,以及不能是refresh token if payload and not payload.get('grant_type') == 'refresh': return True, payload return False, None 参考链接 理解JWT(JSON Web Token)认证及实践 理解OAuth 2.0[1] References [1] 理解OAuth 2.0: http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html ...

2019-04-29 · 1 min · 204 words

Solidity 简易教程0x001

Solidity是以太坊的主要编程语言,它是一种静态类型的 JavaScript-esque 语言,是面向合约的、为实现智能合约而创建的高级编程语言,设计的目的是能在以太坊虚拟机(EVM)上运行。 本文基于CryptoZombies,教程地址为:https://cryptozombies.io/zh/lesson/2 地址(address) 以太坊区块链由 account (账户)组成,你可以把它想象成银行账户。一个帐户的余额是以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样。 每个帐户都有一个“地址”,你可以把它想象成银行账号。这是账户唯一的标识符,它看起来长这样: 0x0cE446255506E92DF41614C46F1d6df9Cc969183 这是 CryptoZombies 团队的地址,为了表示支持CryptoZombies,可以赞赏一些以太币! address:地址类型存储一个 20 字节的值(以太坊地址的大小)。 地址类型也有成员变量,并作为所有合约的基础。 address 类型是一个160位的值,且不允许任何算数操作。这种类型适合存储合约地址或外部人员的密钥对。 映射(mapping) Mappings 和哈希表类似,它会执行虚拟初始化,以使所有可能存在的键都映射到一个字节表示为全零的值。 映射是这样定义的: //对于金融应用程序,将用户的余额保存在一个 uint类型的变量中: mapping (address => uint) public accountBalance; //或者可以用来通过userId 存储/查找的用户名 mapping (uint => string) userIdToName; 映射本质上是存储和查找数据所用的键-值对。在第一个例子中,键是一个 address,值是一个 uint,在第二个例子中,键是一个uint,值是一个 string。 映射类型在声明时的形式为 mapping(_KeyType => _ValueType)。 其中 _KeyType 可以是除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。 _ValueType 可以是包括映射类型在内的任何类型。 对映射的取值操作如下: userIdToName[12] // 如果键12 不在 映射中,得到的结果是0 映射中,实际上并不存储 key,而是存储它的 keccak256 哈希值,从而便于查询实际的值。所以映射是没有长度的,也没有 key 的集合或 value 的集合的概念。,你不能像操作python字典那应该获取到当前 Mappings 的所有键或者值。 特殊变量 在 Solidity 中,在全局命名空间中已经存在了(预设了)一些特殊的变量和函数,他们主要用来提供关于区块链的信息或一些通用的工具函数。 msg.sender msg.sender指的是当前调用者(或智能合约)的 address。 ...

2018-10-22 · 7 min · 1349 words

Solidity 简易教程

Solidity是以太坊的主要编程语言,它是一种静态类型的 JavaScript-esque 语言,是面向合约的、为实现智能合约而创建的高级编程语言,设计的目的是能在以太坊虚拟机(EVM)上运行。 本文基于CryptoZombies,教程地址为:https://cryptozombies.io/zh/ 合约 Solidity 的代码都包裹在合约里面. 一份合约就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点. 一份名为 HelloWorld 的空合约如下: contract HelloWorld { } hello world 首先看一个简单的智能合约。 pragma solidity ^0.4.0; contract SimpleStorage { uint storedData; // 声明一个类型为 uint (256位无符号整数)的状态变量,叫做 storedData function set(uint x) public { storedData = x; // 状态变量可以直接访问,不需要使用 this. 或者 self. 这样的前缀 } function get() public view returns (uint) { return storedData; } } 所有的 Solidity 源码都必须冠以 “version pragma” — 标明 Solidity 编译器的版本. 以避免将来新的编译器可能破坏你的代码。 例如: pragma solidity ^0.4.0; (当前 Solidity 的最新版本是 0.4.0). 关键字 pragma 的含义是,一般来说,pragmas(编译指令)是告知编译器如何处理源代码的指令的(例如, pragma once )。 ...

2018-09-04 · 4 min · 719 words