过去的几天一直在回顾整个产品团队过去一年所做的工作,有的工作有亮点,有的工作可以说是乏善可陈。对于不好的,发现其中的一个核心原因就是没有坚持“以终为始”的原则。现将我2021年10月写的一篇公司内部博客再次分享给团队,也分享给所有关心TDengine发展的开发者和朋友们。
亚马逊的两位前高管Colin Bryar与Bill Carr离开亚马逊后,前不久出版了一本书:"Working Backwards: Insights, Stories, and Secrets from Inside Amazon", 让大家对亚马逊的工作原则之一”Working Backwards"有了更多了解。按照中国人的说法,亚马逊的这条工作原则其实就是“以终为始”。做任何事情,先想到结果和目标,然后倒推怎么达到这个目标,之后再启动执行。
Working Backwords是一提高工作效率的极为有效的方法,因为几点:
做任何事情,我们一定要先清楚目的和目标;
很多时候,我们对问题或任务只有一个模糊而不清晰的了解;
如果对要解决的问题和任务有清晰的描述,其实问题已经解决了一半;
不仅问题很清晰,而且解决的步骤、计划反复推演了,问题已经解决了大半;
初期的投入时间多,都大大节省了后续的时间,而且让任务的完成时间可预期。

细看涛思数据过去4年多的发展历史,如果仔细分析,我们所有高效完成的任务一定是有意或无意中执行了“Working Backwards"的原则。对于效率差的,一定是偏离了这条原则。大道理一听就明白,落实到我们具体工作上,特别是产品的研发上,我们要怎么做呢?对于任何一项新功能,大版本的开发,我们应该有一个什么样的工作流程呢?按照这条原则,我们做事的先后顺序是:
1:起草新功能、大版本的宣传稿
这个新闻稿不能太长,原则上不能超过一页。在这个简短的宣传稿里,我们需要给用户清晰的介绍:
新的功能和特性;
这些功能和特性能给用户解决什么问题;
这些功能和特性能给用户带来的价值,列出三点而且至多三点;
这些功能和特性与竞品相比,好在哪里,列出三点而且至多三点;
这个新闻稿,不仅自己看,还要给销售、BD同学看,给潜在的客户看,看是否能打动他们体验一下产品。如果没人动心,说明工作的价值不够,或者是亮点没有突显出来,让大家意识到它的价值。
2:起草好用户手册
用户怎么使用这个产品至关重要,具体而言,用户手册需要包含:
用户有那些方式使用,SQL的支持, connector, API, 编程语言等;
用户如何配置进行个性化设置;
用户如何监测产品的运行;
用户如何从老版本升级;
用户如何从其他系统迁移;
用户如何快速验证性能指标
只有起草好用户手册,我们才真正的将需求细化、落实,才能与需求方完全确认是否满足需求。也只有起草好用户手册,测试团队才能尽早介入。
3:模块划分,定义模块API
产品定义清晰了,就需要真正做设计了,进行模块划分。怎么划分模块,包括源文件的目录结构,有自己的规则。但无论如何划分,我们需要做到:
模块对外服务的API有清晰的定义,输入参数、输出参数,是否线程安全等,都有清晰说明
模块的性能预期,验证工具
模块对外的依赖很清晰
划分了模块,而且定义了模块的API,实际上就是将模块的功能完全确定了,也就是对模块的需求完全确定了。更进一步,我们需要马上将所有模块集成,所有模块的API都可以是一个dummy的实现,而且马上将整个系统纳入到公司的CI/CD流程,要让它跑起来。这样做,有几个明显的好处:
如果测试组人手宽裕,这个时候就可以介入模块的功能测试和性能测试了;
任何人都不能随意修改API,而影响依赖它的模块;
人手不够,可以将某个模块外包,或先用第三方软件代替;
对于极为关键的模块,可以启用两个team并行开发;
4:模块本身的设计
定义好模块对外服务的API后,我们就可以着手模块本身的设计了。模块设计的原则在本博客不做讨论。
5:编码、测试,不断迭代
在模块设计定稿后,就可以开始编码,进行模块测试了。而且由于CI/CD已经跑通,随时可以跑整个系统的测试,每天都会看到有新的PR,有功能在实现。
以上五步,其先后顺序极其关键,必须按照顺序执行。表面上看,这个流程与我们倡导的敏捷流程有矛盾,但其实不是。敏捷流程强调的是一个一个Sprint完成工作,是小步快跑。上述流程是更大的产品开发流程。
在我们以前的产品研发过程中,我们都没有主观意愿按照这个顺序进行,必须纠正过来。
除产品研发工作之外,我们在销售、市场的工作也应该执行这个工作原则,而且这个工作原则与我倡导的《做事的四个原则》是完全一致的。可以说,"Working Backwards"这个原则在销售、市场工作的更具体的执行,是《做事的四个原则》。
陶建辉
2021年10月3日于北京望京
👇 点击“阅读原文”,体验 TDengine 3.0!
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
所以我开始关注ruby,很多东西看起来不错,但我对隐式return语句很反感。我理解默认情况下让所有内容返回self或nil但不是语句的最后一个值。对我来说,它看起来非常脆弱(尤其是)如果你正在使用一个不打算返回某些东西的方法(尤其是一个改变状态/破坏性方法的函数!),其他人可能最终依赖于一个返回对方法的目的并不重要,并且有很大的改变机会。隐式返回有什么意义?有没有办法让事情变得更简单?总是有返回以防止隐含返回被认为是好的做法吗?我是不是太担心这个了?附言当人们想要从方法中返回特定的东西时,他们是否经常使用隐式返回,这不是让你组中的其他人更容易破坏彼此的代码吗?当然,记录一切并给出
给定以下方法:defsome_method:valueend以下语句按我的预期工作:some_method||:other#=>:valuex=some_method||:other#=>:value但是下面语句的行为让我感到困惑:some_method=some_method||:other#=>:other它按预期创建了一个名为some_method的局部变量,随后对some_method的调用返回该局部变量的值。但为什么它分配:other而不是:value呢?我知道这可能不是一件明智的事情,并且可以看出它可能有多么模棱两可,但我认为应该在考虑作业之前评估作业的右侧...我已经在R
我在我的Rails3示例应用程序上使用CarrierWave。我想验证远程位置上传,因此当用户提交无效URL(空白或非图像)时,我不会收到标准错误异常:CarrierWave::DownloadErrorinImageController#createtryingtodownloadafilewhichisnotservedoverHTTP这是我的模型:classPaintingtrue,:length=>{:minimum=>5,:maximum=>100}validates:image,:presence=>trueend这是我的Controller:classPaintingsC
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD