fitzyu / technote

My technique notebook.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

前言

本文是对我自己及团队成员工作上的一些指导或要求,欢迎补充和改进。

总结来说就是: Be Professional!

必读书籍资料

短期

这些是进入项目就要用到的,请务快速必掌握。

长期

  • 《代码整洁之道》
  • 《重构:改善既有代码的设计》
  • 《深入理解计算机系统》
  • 《敏捷软件开发 : 原则、模式与实践》
  • 《企业应用架构模式》
  • 《TCP/IP 详解 卷1》
  • 《Linux 内核设计与实现》
  • Git
  • the-art-of-command-line
  • 《搞定:无压工作的艺术》

必备工具

翻墙

方式很多,建议购买国外 VPS 自建 shadowsocks ,服务端推荐使用搬瓦工 ,客户端 MacOS 使用 https://github.com/shadowsocks/ShadowsocksX-NG ,Ubuntu 使用 https://github.com/shadowsocks/shadowsocks-qt5 配合 Chrome Proxy SwitchyOmega 插件,具体搭建方式自己搜。

不建议直接使用付费翻墙服务(shadowsocks 、VPN 等),不稳定,容易被关停。

Sublime Text

http://www.sublimetext.com/3

安装 Package Control:https://packagecontrol.io/installation ,必备插件:Git,GitGutter,knockdown,SublimeLinter,DocBlockr,EditorConfig,Emmet,FileDiffs,SublimeCodeIntel, SidebarEnhancements

可以将整个配置目录放到 GitHub 上,方便在多台机器上同步,比如 https://github.com/inetfuture/sublime-config ,配置目录的位置:

  • Linux:~/.config/sublime-text-3/Packages/User
  • OS X:~/Library/Application\ Support/Sublime\ Text\ 3/Packages/User

或者使用 https://code.visualstudio.com/

印象笔记

https://www.yinxiang.com/

不建议使用 Evernote 国际版,太卡,而且国内的版本提供的本地化服务更方便。

Chrome 插件

其它

开发环境配置

安装软件

  • 优先使用语言版本管理器安装语言运行时,比如 nvm(Node.js),pyenv(Python),rvm(Ruby),jenv(Java),phpbrew(PHP)

    注意,此类工具通常安装在你的 $HOME 下,通过修改当前 shell 的 $PATH 等环境变量生效,就是说安装或者使用它们的时候,你不需要也不应该使用 sudo

    好处:

    • 不需要 sudo,不会污染系统环境,如果某天坏掉了,可以直接删掉重来。
    • 方便版本切换,及时用上最新版本。
  • 优先使用语言包管理器安装语言依赖,比如 npm(Node.js),pip(Python),gem(Ruby),maven/gradle(Java),pecl/composer(PHP)

  • 优先使用系统包管理器安装系统依赖,apt-get(Ubuntu),homebrew(OS X)

  • Ubuntu 必备软件包:sudo apt-get install -y aptitude gdebi vim curl wget build-essential openssh-server

做事原则、方法

  • 主动沟通而不是被动应付
    • 工作上存在任何让你不爽的事时,尽量提出来,所有的问题都可以讨论、协调、缓解,甚至完全避免,长期压抑着会影响自己的工作效率和积极性,对个人和团队都不利。

      举个例子,之前参与的一个项目,使用的技术栈过于复杂,同时使用了 PHP,Python,Node.js,导致开发效率低,维护也很麻烦,我很不爽,还好老大比较开明,经过沟通后,决定全部替换为 Node.js,从此我就再也不用纠结这个事情。

    • 对需求、开发规范、代码组织等等各方面存在任何疑问时,主动询问和讨论,不要默不作声、自作主张导致返工和延误。

    • 自己的代码被别人 Review 时,如果觉得某段代码可能有问题或者不是最优方案,主动提出来讨论,而不是等待审查者询问。前者可以使 Review 更高效,更利于培养同事间的相互信任,后者一是效率低,同时也是一种不负责任的表现。

    • 进度存在风险时主动知会其他人,而不是默默拖到最后一刻连累大家加班。

    • 做变更时及时通知相关人员,否则可能会浪费队友的时间去排查问题。代码级别的问题可以通过在 GitLab 等代码管理工具上 at 对方。

  • 认真负责,关注细节,追求卓越
    • 每一份需求都要理解之后再动手,感觉需求不合理或不明确的要提出来讨论,对产品要有责任心,不仅仅是“按部就班”得做事情。
    • 提交前仔细测试自己实现的功能,检查代码细节,有交付高质量代码的责任心,而不是依赖其他成员帮你 Reivew 或者 QA 帮你发现 bug 。
    • 代码仅仅可以工作是不够的,还要可读、易扩展、可维护、安全、高效,等等等等,要用高标准要求自己,写代码要“上心”,不要敷衍、随意。
    • 细节不一定决定成败但却可以充分体现一个人的工作态度,请认真对待各种流程、规范,比如写英文邮件时,从语法、格式到每一个拼写、标点,再比如 Commit Message 的格式、Coding Style 等等。
  • 自我驱动
    • 自觉深入学习相关技术,而不是用多少学多少,临时抱佛脚。假设你在做 iOS 开发,即便目前所有项目仍在用 Objective-C ,你也应该利用业余时间自觉去学习 Swift ,时刻准备着。
    • 主动思考产品的走向,自觉进行知识储备、调研。
    • 自觉重构低质量代码,改善项目流程,等等,保证项目的健康发展。
  • 换位思考
    • 使用邮件、微信等工具交流时一次性提供必要的上下文,避免低效率的沟通,想一下,我这样描述对方是否可以理解并直接回复。
    • 无论是做 Code Review 还是提交功能给 QA 测试,尽自己最大努力保证质量,做好自己分内的事,减轻队友的负担。
    • 作为开发,想一下我该怎样为 leader 分担压力?作为 leader ,想一下我该怎样促进下属工作能力的稳步成长?
  • 从根本上解决问题
    • 思考问题的原因,不要停留于表面问题的修修补补。

      比如,QA 报了一个 Bug,说在一个输入项中填入 <script>alert('haha');</script> ,保存后页面上会弹出提示框,你来修复这个问题的时候要考虑从根本上杜绝脚本注入问题,比如使用 Angular.js 的 $sanitize service 过滤输入再显示。相比较而言,不太恰当的做法是仅仅用正则过滤掉输入中的 <script /> 标签,虽然可以修复这个 Bug,却是治标不治本,如果输入中包含 onmouseover="alert('haha')" 这种代码呢?

      当然这种问题需要你有扎实的基本功,对开发各个方面有较全面的了解,才能一针见血的解决问题。

    • 每一个问题,想一下有没有办法一劳永逸?或者自动化?

    • 谁痛苦,谁改变。某件事总是让你很不爽?想办法改变它!

  • 用正确的方式解决问题
    • 很多问题都有不止一种的解决办法,不要满足于你最初想到的那种,也许有更好的呢?主动去思考目前的方案可维护吗?方便吗?效率高吗?普适吗?

    • 多看一些最佳实践(Best Practice)的资料,多看一些优秀开源项目的源代码,多了解别人怎么做的你才能及时发现自己的不足。

      比如 RESTful API,是有一套业界公认的最佳实践的,其本身就是一套约定俗成的东西,如果你没有看过这些最佳实践,随意的设计 URL,随意的使用 HTTP 动词,那你设计出来的 API 其实只是 HTTP API,请不要称其为 RESTful API。

      再比如你对 Git 不熟,然后不知怎么得把工作目录搞的一团糟,你该怎么办?再 clone 一个重新开始?NO!除非你打算一辈子这么干。你应该去 google 解决方案,并充分认识到自己对 Git 不熟这个事实,然后拿出时间来补充知识。

    • 不要重复造轮子,尤其是使用第三方库时,也许人家已经提供了现成的解决方案,只是因为你没有仔细看文档,所以不知道。

  • 磨刀不误砍柴工
    • 学习新知识要尽可能的系统、全面,不要只是为了应付当前工作片面了解。
    • 使用第三方库、框架时应尽量通读其文档,至少要知道它可以做什么,有哪些限制,遇到具体问题后可以迅速到文档中查看细节。
    • 常用的技术点要舍得花时间搞清楚其运行原理、内部机制,免的每次出问题都像无头苍蝇,只能瞎猜。比如 Angular.js 的 directive 编译流程是怎样的?Node.js 的异步 IO 是如何运行的?
    • 工欲善其事,必先利其器。平时要注意效率工具的积累,包括好的代码编辑器、命令行工具、任务管理工具、各种实用小工具等等。对于你常用的编辑器或 IDE 应该花一些时间去熟练它的快捷键,寻找一些好用的插件,毕竟工作每天都要用,投入的精力绝对物超所值。

编写整洁代码

Writing clean code requires the disciplined use of a myriad little techniques applied through a painstakingly acquired sense of "cleanliness". This "code-sense" is the key. Some of us are born with it. Some of us have to fight to acquire it. Not only does it let us see whether code is good or bad, but it also shows us the strategy for applying our discipline to transform bad code into clean code.

基本要求

  • Coding Style,如果有要求,应该严格遵循,任何例外的情况需要讨论决定。
  • 可读性,要容易理解,命名要具有足够描述性,不能有歧义,代码路径、结构要清晰、简洁。
  • 一致性,包括但不限于标识符命名、错误处理、日志格式、文件组织方式、HTTP API 接口设计、UI 交互等各个方面,越是一致的系统越容易上手,越容易维护,反之则维护成本越高。
  • 健壮性,进行必要的输入验证(不要过度处理),充分得考虑边界情况,异常处理要周全,防止内存泄露,防止竞态条件,多线程安全,等等。
  • 性能,考虑数据量大或者访问频繁时的情况,对内存、数据库的使用要高效,算法要尽量最优。
    • 任何涉及数量的地方,在业务场景合理的前提下考虑把数量放大到最大,为最坏的情况做打算。
    • 数据库
      • 操作应尽量批量进行,只查询必需的字段,减少 IO 消耗。
      • 特别大的查询应在数据库中分页,由程序控制分批次处理,全量取出在内存中计算或者根本不考虑数据量大小是常见的低级错误。
      • 合理使用索引。
    • 基本优化思路:
      • 预先筛选数据,减少不必要的计算
      • 缓存计算结果,减少重复计算
      • 使用高效数据结构,空间换时间
  • 安全性,进行必要的权限检查,不能过度信任客户端输入。
  • DRY(Don't Repeat Yourself)原则,复制、粘贴的行为是要坚决禁止的,不知道如何复用代码的要主动与其他成员讨论。
  • 单一职责原则,一个类、文件或者模块不能做的太多,不能做不该它做的事,好的设计是只把一件事做好。
  • 封装原则,不要暴露不必要的实现细节。
  • 开放、封闭原则,要方便扩展,要考虑到以后的需求。
  • 代码改动方式要合适,不能一味得堆砌代码,需要适时停下来进行重构。
  • 保持干净,不能存在任何无用的文件、代码,所有文档、注释需要同步更新,不能包含注释掉的代码,不能包含临时调试代码,例外情况应该添加注释说明。
  • 所有 Warning 都应该被立即修复,觉得不需要修的,讨论决定后通过修改配置文件禁用掉。

进阶要求

提高代码可读性的技巧

  • 局部变量尽量就近声明。

  • return early, https://www.airpair.com/php/posts/best-practices-for-modern-php-development#4-2-try-not-to-use-else-, http://www.codeproject.com/Articles/626403/How-and-Why-to-Avoid-Excessive-Nesting

  • 在语言本身语法允许的情况下,将主流程放在文件上部,子流程按被调用顺序放在文件下部,这样打开文件后可以比较快的抓住重点,比如:

    main = ->
      doStuff1()
      doStuff2()
      doStuff3()
    
    doStuff1 = ->
      console.log '1'
    
    doStuff2 = ->
      console.log '2'
    
    doStuff3 = ->
      console.log '3'
  • 相关的代码尽量按使用顺序组织在一起,尤其是添加新代码时,不要一味得添加到文件尾部。

  • 布尔变量命名应尽量采用肯定形势。

  • 避免硬编码数字、字符串,应使用常量并给它们有意义的名字。

  • 传递简单数据类型时,适当添加临时变量提高可读性,例如:

    ignoreError = true
    doStuff(ignoreError)

如何写注释

  • 代码的意图应该由代码自身来表达,即所谓的可读性,不应该依赖于注释说明,所以优先考虑写更可读的代码。
  • 代码意图明显的情况下,不要加注释重复说明。
  • 以下注释是合理的或者说以下情况需要写注释:
    • 纲要性的注释,简要的描述某一个文件、某一个类或某一个流程。
    • 确实无法从代码本身提高可读性的情况,比如复杂业务逻辑、算法。
    • 代码的作用并不直观时,解释这样做的原因。
    • 存在多种可选方案时,解释为什么选择现在这种。
    • 因为某些限制而使代码不一致、不优雅或存在副作用时,应注明原因及后果。
    • 参考了外部一些资料时,应注明链接,方便其他人查看。
    • 临时标记注释:TODO、FIXME、HACK、OPTIMIZE、REVIEW
  • 注释应随代码更新。

Code Review

目的

  • 提高代码质量,查漏补缺。
  • 相互学习。
  • 促进项目内知识流动,防止对某个个人过分依赖。

Commit Message 规范

规定格式如下:

$(scope): $(subject)

$(description)
  • $(scope):必需,取决于具体项目,一般为项目功能模块、组件的名字,用来描述本次 commit 影响的范围,比如 https://github.com/nodejs/node/commits/masterhttps://github.com/golang/go/commits/master 。嵌套层级结构可以用 / 表示,如 net/http。影响多个模块、组件可以用 , 隔开(不加空格),如 net/http,cmd。后加入项目的新成员应遵循已有的 scope 约定(通过 git log 可以查看某个文件的提交历史),不要自己编造。使用首字母小写的驼峰命名。bughotfixtaskchangerefactor 等等描述的都不是影响的具体范围,而是改动类型,不能用作 scope 。除具体的模块、组件名之外,可以使用 base 表示基础结构、框架相关的改动,用 misc 表示杂项改动,用 all 表示大范围重构。
  • $(subject):必需,50 个字符左右的简要说明,首字母小写,通常是动宾结构,描述做了什么事情,动词用一般现在时,禁止出现 update codefix bug 等无实际意义的描述,好的例子: select connector by sorting free memory (不需要形如 update about how to select connector ... 的啰嗦写法), fix sucess tip can not show on IE8 (不需要形如 fix bug of ... 的啰嗦写法)。
  • $(description):可选,详细说明,建议使用列表罗列要点。

流程

  1. 提交者发起 topic 分支到目标分支的 Merge Request 。
    • 代码变动要尽量小且专注于一个任务,不要攒的很大,或者做多个任务,要保证审查者可以较快、较容易的 Review 。
    • 如果与目标分支有冲突,提交者应该自己使用 git rebasegit merge(共享分支的情况)解决。
    • 交给别人之前一定要自己先 Review 一遍,别人只是帮你查漏补缺,对自己的代码负责,不要浪费别人的时间。
    • 发起后,要在 GitLab 或者其它 Review 工具上 double check 变更集。
  2. 审查者 Review 代码。
    • 编写整洁的代码 中各项要求进行检查
    • 在任何有疑问或建议的地方留 comment。
    • 从中学习一些好的东西。
  3. 提交者响应 comments 。
    • 确实有问题的,修复之。如果该分支未被其他人使用,应使用 git commit --amend 提交以减少不必要的 commit 历史(--amend 选项表示修改上一个 commit 而不是创建一个新的 commit ,commit 被修改过后,git push 必须加 -f 强制推送才能 push 成功)。
    • 不同意的,讨论。
    • 完成后,留 comment “fixed”,审查者再次检查,回到第二步。
  4. 审查者确认没有问题之后,将 Merge Request 转发给目标分支的维护者进行合并。

调试技巧

  • 查看日志,比如做 PHP Web 开发要知道 Nginx,PHP-FPM,PHP 的日志文件的位置,必要的时候从中寻找线索。使用 tail -F file1 file2 命令可以持续监控多个文件。
  • 通过添加临时 log 语句或断点的形式检查代码路径,很多时候调试是个体力活,并没有什么难度,不要轻易放弃或寻求他人帮助。检查代码是否按预期路径执行了,如果没有,为什么?输入数据的原因吗?或者中间一步数据处理是错的?从数据进入系统开始一步步从前往后分析,用排除法逐步缩小范围,bug 必将无所遁形。另外,有时候代码路径可能牵扯到第三方库,这个时候不要畏惧,代码都是人写的,尤其开源项目通常质量较高,进去看一下,通常没你想象的那么难。
  • 确保读懂日志消息、异常信息、错误输出等,特别是英文内容,不要因为是英文不想读,结果非常明显的线索摆在你面前你却视而不见。比如常见的 git 错误,都会有相应的描述甚至建议的解决办法。再比如做 Web 开发,一个页面打不开,最起码你要先看一下 HTTP Response 是什么,状态码,body 等。如果实在看不懂,google 之。
  • 有意识的组织整理常见错误,依据过往经验快速定位问题。例如 PHP 开发时碰到 HTTP 404,基本可以排除代码逻辑问题,应该检查拼写错误、Nginx 配置、MVC 框架路由配置、文件路径等。
  • 使用第三方组件时,遇到问题要知道去查看它的 issue 列表,也许别人也遇到了同样的问题并且已经报了 Bug ,尤其是 GitHub 上的开源项目。

Research 技巧

工作中难免需要学习新技术,这个过程中也有一些技巧可以实践。

  • 使用 Google 搜索,除非是特定于国内的东西,比如微信开发相关,不要使用百度。

    百度搜索质量偏低是事实,一方面是受限于百度自身的技术,另一方面是因为中文搜索有很多垃圾站,以及低质量的转载博客。经常出现前几页都是各种垃圾站在重复同一篇文章,而且广告满天飞,排版稀烂。还有新浪、网易的博客,很多都是复制粘贴的转载,格式一团糟,原文却不知所踪。更不要提很多技术中文资料相对较少,更新不及时,翻译质量不高等等问题。

    养成使用在 Google 上使用英文关键词搜索的好习惯绝对可以节省你的大量时间和精力。

  • 搜索时要注意时效性

    取决于具体的技术,搜索出来的东西很多可能已经过时了,要注意区分,不要被过时的东西误导。快速浏览搜索结果时捎带看一下时间也是一个好习惯,有多条相似结果时当然是最新的会靠谱一点。

  • 掌握使用搜索引擎的一些小技巧,http://www.lifehack.org/articles/technology/20-tips-use-google-search-efficiently.html

  • 注意搜集、整理一些高质量的网站、博客

    当你研究一个新东西的时候肯定会进行很多搜索,其中有些结果可能质量很高,这时可以快速浏览一下这个网站或者博客的首页,看看是否有收藏价值,或者有没有其它相关文章值得看一下。

  • 阅读权威、系统的书籍

    通过使用搜索引擎或者阅读博客等通常可以对某种技术有个大概了解,或者可以解决某个具体问题,但是对于较复杂的技术,系统学习是必不可少的,看书往往是最有效的方式。可以到豆瓣阅读或者各大电商网站上通过评分排名来筛选好的书籍。如果是国外书籍,尽量选择英文原版,一开始会比较吃力,熟悉了就好了。

    跟筛选搜索结果一样,尽量选择较新的书籍。

  • 持续关注

    每个人理解新事物都有一个从浅入深的过程,新学的技术最好可以不断夯实,反复咀嚼,不要停留在表面。可以通过订阅一些邮件列表、知名博客来不断补充知识盲点,学习最佳实践,反思自己的不足,以及了解技术的最新动态。

沟通技巧

目标是提高沟通效率,节省时间。

  • 别扭扭捏捏,放开声音好好说话。

  • 组织好语言,主语、谓语、宾语、状语要清晰,尽量少用 “TA” ,“这个”,"那个"等指称代词,话要说全。

  • 尽量提供完整的上下文,前因后果交代清楚,尤其是对话刚开时的时候,不要急于切入重点,先确保对方了解足够的背景,避免不必要的背景交代相关的往复提问和回答。

    有时新人会喊我去帮忙调试问题,经常遇到上来就跟我讲“这个地方报错,你帮我看一下”,然后就没了。这个时候正确的姿势是交代清楚你在干什么,想达到一个怎样的效果,改了哪些文件,尝试了哪些方式,结果是什么,如果已经添加了一些调试日志的话,就要从前到后把所有加日志的地方介绍一遍,最后是重现一遍这个问题。

    另外比如报 bug ,哪个环境、哪个页面、做什么操作、具体报什么错(没反应?404?500?)统统不提,只说 “xxx 挂了”,跟这种人对话让我想自杀。

    再比如索要信息,请交代你的意图、使用场景,否则凭啥给你?客户要?客户要来干嘛?

    这种场景很多,无法穷举,总的原则是:在你把问题抛出去之前,设想一下对方可能对哪些地方有疑问,对方可能反问什么,你提供的信息足够对方做出判断、决定吗,然后再补充。要谨慎,不要随意。如果做不到这一点,你就是在浪费别人的时间,惹人讨厌。

  • 问问题时,除了问实际的问题外,最好也解释一下这个问题的来由,这个问题哪儿来的,为什么有这个问题或疑问。这样通常可以帮助回答者更能抓住问题的重点,更准确的回答。

  • 回答问题时,对于是或否的疑问句,尽量先直接了当的回答,然后有必要的话再补充或解释。

    比如有人问“这个文件现在有改动吗?”,不合适的回答是“上午改过,遇到了点问题,又改回去了”,这种沟通我觉得是比较低效的,试想如果是一个比较复杂的场景,可能提问者听半天解释也得不出答案。对于这个问题,提问者关心的是现在它有没有改,应该先回答“没有”,然后如果有必要,再补充说“但是上午改过,遇到了点问题,又改回去了”。

  • IM 消息组织好再发,不要一条一条蹦,尤其不要问“在吗?”,一旦第一条发出去,对方可能已经被打扰,然后 TA 要停下在做的事情,等着你敲完?另外,发出去前检查,不要有错别字。

  • 回答进度估计等相关问题时,不要使用“差不多”、“快了”等模糊词汇,应该具体一点,比如“90% 都做完了,还剩xxx,下午可以完成”。

  • 多人参与的事务,最好在公开渠道沟通,比如邮件、issue 或 IM 群组等,该开会的时候开会,避免跟多个人私聊同一件事情而反复交代上下文,一是累,二是容易遗漏,三是浪费时间,应尽量保证沟通透明、信息对称。

    • 通常来说,只有涉及敏感信息、明确需要把某些人排除在外、确实与其他人无关的时候,才私聊。
    • 不要担心会打扰不相关的人,因为如果这个人不相关,TA 要么不要关注这个 issue (多数 issue 系统都可以 unsubscribe 某个 issue),要么离开这个 IM 群组,要么设置自己的通知级别为“仅提到”,总之是这个人自己的事情。
    • “设置通知级别为仅提到”只适用于 IM 群组,不要用于 issue 系统全局设置,否则别人每次回复必须再 at 你,很不友好。
    • 注意 IM 群组的主题,必要的话创建专门的新群组,不要在一个群组里讨论与这个群组主题或其中大部分人无关的事情。
  • 在邮件或 issue 上对某人发起请求后,不要再跑到 IM 上去提醒,尤其不要在公共 IM 群组提醒,这种提醒是纯粹的噪音,浪费屏幕空间,浪费其他人的时间。

    • 担心对方不看、不回怎么办?如果你有期望回复的最晚时间,那么直接写明,否则对方可能确实在忙,把你的往后排了;如果对方经常长时间甚至永远都不回复,非要等你通过其它渠道催,那么不管你有没有指明期望回复时间,都是对方的问题,响应其他人的请求是基本的职业素养,提醒 TA 注意处理,但是绝对不要容忍每次都要提醒,绝对不要默许这种费劲的沟通方式,这种人是在浪费大家的时间,甚至可能带坏其他人。
    • 理想情况下,每个人都会保证请求的及时响应,也就不需要无谓的担心和反复确认,沟通噪音就会少很多。事情太多,老是忘怎么办?你需要一个 TODO 管理工具,不能立即处理的先记下来,定期清理。
    • 怎么算及时?看情况,一般来说,请求方未明确期望回复时间的前提下,一工作日内能回复就算及时,明显不重要、不紧急的事务几个工作日内能回复即可。
  • 尽量不要使用微信沟通,推荐使用 https://bearychat.com/ 或自行搭建 https://rocket.chat/ ,原因:

    • 历史记录只保存在客户端,易丢失,不易搜索。
    • 新成员看不到历史消息,有时候沟通历史很重要,可以看到一件事情如何演变的,为什么是现在这个样子。
    • PC 端只能登陆一个设备,家里公司要频繁切换登录,还必须在手机上确认(WTF?)。
    • 功能有限,不支持机器人,不能高亮代码,想只复制消息的某一部分都费劲。
    • 至于微信方便在手机上使用,根本是个伪需求,你天天在手机上工作?

Get Things Done

TODO

About

My technique notebook.