沟通的意愿

一个玩乐队的朋友最近告诉我说他们乐队解散了,原因是乐队里的3个人对很早就计划的一次中国巡演产生了分歧。这个巡演是一个持续1个多月的活动,但是所有费用自理,还得给一个主办方交上一些钱。鼓手认为这个钱花得有点不值得,但是这个人一贯是不愿让人失望的性格,所以一直没有明确表态,直到最后一个月内才提出这个意见。乐队的吉他手很想去,然而这个人一贯地不是很在乎别人的感受和意见。我这个朋友一直一来很清楚这两个人的性格特点,但是却也没有特别刻意去管理他们的关系,平时大家也可能因为乐队里的小事产生分歧,然而因为都是小事,如果事关钱的话金额也不大,所以一直都相安无事,而此次巡演需要每人好几千的投入,因此矛盾一下子就激化了,最终吉他手变得过度情绪化,乐队也遗憾地解散了。

其实我这个朋友一直一来清楚其他两个人的性格特点可能会给乐队带来问题的,但是他并没有未雨绸缪。和他聊天的时候,我联想到了自己之前的一个经历,也是和一个朋友一起利用业余时间做一个小的rails web项目。当时我俩自认经验丰富,又以为rails项目做起来刷刷地快,我们的这个弹丸项目必然是两三刀就可以搞定了。因此我们并没有像做公司的项目一样,讲究认真调研需求,拆分任务,测试驱动,而是基本上直奔着去写代码了。我能够感觉到这样做可能带来的后果,但是因为一系列原因(因为是业余项目,因为是rails,因为小,因为这只是第一个发布还不知道有没有人会用等等),我并没有和我的朋友积极沟通这些问题。结果我们因为没有自动化测试,花了很多功夫反复修复一些细小bug,拖慢了我们的进度也影响了我们的心情,这就是后话了。

造成这些问题的一个根本原因就是沟通不够。而且这两个故事里,我和我这个玩乐队的朋友都不是没有察觉问题,没有沟通能力,而是没有沟通的意愿。缺乏意愿的原因有很多,其中一种大概是缺乏足够的关注。简单地讲,如果不足够在乎某件事情,不愿意投入足够的精力去做这件事情,那么对这件事情的结果的关注也就弱化了,自然也就不会投入过多的精力去沟通解决潜在的问题。

如果我们明确地知道自己能够投入的精力也有限,知道对结果也只能抱有有限的期望,那也无可指责。问题是很多时候人们并没有意识到这一点,相反往往是对结果抱有美好的期望的。例如希望能够做出一个很棒的startup产品,或者希望乐队将来能够有一番成就,抱有美好的期望却没有正视自己所应该做的投入,这就是一件遗憾的事情。如果我当时能够像对待白天工作中的项目一样对待业余时间的项目,那么我们也就不会有当时的那些问题。

这个原因也体现在另外的一种情形中,比如说一个人知道当前团队的一些问题,如果提出来讨论改进可能会让当前的情况好很多,但是这样做需要耗费他很多精力,并且可能没有结果,最重要的事,对自己没有什么直接的好处。这样的情况下,他对问题的在意程度也是有限的,以至于不足以刺激他去做出这些沟通的投入。造成这样的结果,是组织同时也是个人的原因。所以有时候我看到那些抱怨公司和团队不好的文章,一方面固然认为他们应该有更积极的态度,另一方面也觉得他们毕竟做出了努力,比起默不作声的那部分人,还是做出自己的投入的。

最后,除了关注度之外,个人的性格也是一个影响因素。有些人在逆境中只是默默承受,或者毫无知觉,有些人会选择跳出当前的小环境,寻找更适合自己的小环境,有的人则可能想去改变这个小环境。最后这类人,又可分为实际有能力和没有能力两种。面对问题和冲突时,能够挺身而出,也是需要勇气的。不是所有的分歧都能够平和地解决,也不是所有的问题最终都有让所有人开心的方案,如果你不喜欢这一点,那么寻找新的小环境也是一种不错的选择。或者如果你足够在乎,就科学客观地认识问题,抛弃情感上的犹豫不决,做正确的事。


持续交付笔记

持续交付笔记第一篇:持续交付概述


敏捷能给团队带来什么,不能带来什么

先谈谈需求。

采用敏捷方法的团队,更有可能准确地用软件帮助客户实现脑子里的想法。然 而,除非团队有能力在某一需求领域内给予客户超出其自身现有经验的建议,否 则最终的软件产品不太可能会实现超出客户当前已经识别出来的需求的价值。

敏捷方法主张快速反馈和积极的客户协作。通过客户的积极参与、和团队的面对 面沟通,团队在软件生产周期的各个阶段(分析、开发、测试、验收等)以尽可能 高的频率获得客户的反馈,并迅速调整,使得生产的软件符合客户的期望。例如 在前期需求分析的时候借助UX的wireframe等工具,将大家脑子里的东西尽可能地 可视化展现出来,以达成准确的共识;在开发阶段,也会不断和客户确认需求的 细节,同时会在一部分功能实现后就迅速寻求客户的反馈。这一切活动都保证了 敏捷团队能够更快地和客户达成需求上的准确的共识,同时以尽可能少的返工和 误差实现客户的需求。

然而这些手段的最大效果,是帮助团队准确高效地实现了客户脑子里的想法。当 然,敏捷所帮助团队实现的更高频率的交付,也能够让客户有机会更快收集到最 终用户的反馈,有机会更快地验证他们最初想法的好坏,如果客户有意愿并且有 能力在这些反馈的基础上做出尽快调整,那么从某种程度上讲,敏捷确实能给客 户带来更高的价值。但是如果客户并不具备这个能力,或者忽略了从最终使用者 的快速反馈带来的好处,那么最终交付的软件也只能实现客户已知的价值。对于 那些对自己的行业和业务非常了解的客户来说,这一目的的达成对他们已经具有 无上价值了,然而如果客户脑中的需求本身就不先进,甚至是陈腐的,他们也许 会最终发现敏捷并不能帮助实现自身认识和现有业务的质的飞跃。

虽然在这个过程中,UX方面的一系列手段帮助我们实现了更好的交互体验(并且很 多技术如mental model似乎还能够帮助我们挖掘出更多最终使用者实际需要的东 西),然而这些也并不是敏捷包含的内容。

在这里我们区分这三种能力:运用敏捷方法交付软件的能力、UX方面的能力、在 某一具体的行业或者业务领域(例如保险、电力、互联网)拥有领先的经验和知 识并能够对这方面的软件提供建议的能力,有这样几个意义:

  • 帮助我们更好地分辨问题出在哪方面。缺什么补什么,不要乱补。
  • 理解清楚敏捷能够给你带来什么,不能带来什么,破除银弹式思维。
  • 如果你现在是作为咨询师的角色帮助团队引入敏捷方法,那么准确识别出当前 团队的问题所在,和团队达成共识,有助于改进的进行。如果团队在业务理解 方面是有欠缺的,那么团队必须知道这一点,不能以为敏捷了以后项目就一定 会成功了。即便咨询师在业务领域、UX、敏捷方法都有丰富经验,能够同时提 供这些方面的建议,也最好对各方面的改进内容有清晰认识。还是回到第一 点,团队必须对自己缺什么,接下来要改进什么有清楚的认识。

再谈谈技术。

敏捷方法在技术实践上倡导重构、演进式设计、重视代码内部质量(干净代码、 没有坏味道)、自动化测试、TDD、持续集成(包括自动化构建、乐观锁SCM工具),最 终获得高质量的代码,降低人工测试的成本,让软件在后期也具备以小成本实现 需求变化的能力。

一个团队如果想熟练应用上述实践,首先得具备这样一些能力和条件。必须懂得 高质量的代码不仅仅意味着没有bug,还得没有坏味道;良好的架构必须是具备可 测试性的,不然TDD和自动化测试难以实现。这也就是说,团队可能会需要提升自 身技术能力,能够识别坏味道,并进行重构;能够理解并运用一些简单设计、OO 设计的原则;要熟悉所用语言的最佳实践;要让自己的软件具备可测试性。具备 了这些基础,团队就可以准备好渐渐享受全面的自动化测试覆盖、频繁地集成验 证、干净的代码架构所带来的好处了。

如果说引入敏捷的好处是让团队具备了上述的这些能力,获得了这些敏捷的好 处,那么我们也可以看出,为了享受到敏捷的这些好处而进行的必要程度的能力 提升,不一定能够保证让团队变得有能力解决好所有架构设计方面的问题。

举几个例子。一个团队自己写了一个url解析和dispatch工具,质量良好,测 试全面,没有坏味道。但是实际上已经有一个不错的开源库存在了,团队因为信 息闭塞,所以不知道这个库的存在;你可能REST风格的web service比soap更好 用,但是团队采用soap web service也写出了质量高、测试覆盖全面的代码;你 认为类似于rails中的ActiveRecord的数据库访问封装方式比hibernate式的封装 更好,但是人家用hibernate式的ORM工具也能够写出质量高、测试全面的软件。

举这些例子都是为了说明,作出良好的架构设计所需要的能力和经验,比顺利应 用敏捷技术实践所需要的设计编码能力和经验要多。这些能力需要长时间的不断 学习、实践来积累,同时还必须对新的技术发展保持敏锐。TDD在某种程度上对 于促成良好的局部设计有作用,然而也不足以驱动我们解决全部的架构和设计问 题。

和前面需求部分的分析一样,之所以我们区分团队引入敏捷所需要具备的技术能 力和做出良好架构设计所需要的能力,我认为有这么几个意义:

  • 敏捷对技术能力有要求,但并没有那么高的要求,你不需要具备软家大师的 能力才能享受到这些敏捷技术实践带来的好处。
  • 也不要认为敏捷能够帮你解决所有软件架构和设计上的问题。团队里必须有 有经验的人来帮助把握技术架构的选择、语言框架的选用等等。

日知其所无,月无忘所能

记录、整理、写作对于个人追求知识学问来说是非常重要的。最近买的一本书里 说,研究学问的人要每天都积极地写作,将每天想到的、读到的、观察到的都要 尝试去总结整理,这样日后也许可以真正用到论文中,即便不可用也是整理自己 的理解和思路的重要手段。

我认为但凡是对追求知识认真的人,也都必须有这一习惯。新的观察和不同环境 对头脑的刺激都有可能产生新知识,而这些知识稍纵即逝。每天读到学到的新东 西如果不整理,也变不成自己的东西。因此我们必须养成勤写、勤记、勤梳理的 习惯。头脑中的东西不腾出来就没有更多空间接受更多新知识,养成勤动手的习 惯会让你的观察更加有效、细致、敏锐,而也只有整理出来,才能讲这些东西变 成自己的,才能将清楚给别人听。

有些人也许像我一样,写着写着会对笔记本特别关注。整理也是一门技术。当你 的图文越来越有价值的时候,你也会开始对你图文的载体表示关注。或者你也会 和我一样,当你手头的信息越来越多的时候,就会很关注如何能将这些信息有效 地组织起来,让以后更方面使用,不至于跟代码一样,长期不重构,就由资产变 成了债务。


TDD from starting from user stories - a top-down style

A rule of TDD is writing tests first. In most real world projects, the software usually contains GUI, web, database and dependencies on external systems. Usually they are not as simple as examples in junit cookbook that can be test driven out by just doing unit testing. There are different kinds of tests in such kind of projects such as functional tests, unit tests, acceptance tests etc. Different projects have different styles of implementing TDD. For example, testers may take the responsibility of writing acceptance tests and developers are only writing unit tests. I would like to advocate a top-down TDD style starting from user stories and ending at production code for TDDers.

TDD from starting from user stories

What

Before we start talking, I would like to achieve a shared understanding of various types of tests with you. We probably have heard about unit test, functional test, acceptance test, module test, integration test etc. By unit test we mean tests that only test one class by mocking out every class it couples with. By functional test we mean tests that test if groups of clusters of classes meet external requirements and achieve goals. Module test and integration test are a kind of functional test that test logically divided module and the integration between modules, and we just treat acceptance test as one of kinds of functional tests which tries to cover a more complete scenario.

Based on our common understanding of these typical kinds of tests in our application, “top-down approach” says developers should write acceptance criteria for the user story first, run it and see the failure, then start to write unit tests, see the unit test fail and fix them. After finishing an amount of unit tests, go back to run the acceptance again, see it pass and then we can say this acceptance criteria has been fulfilled and we can move to next acceptance criteria and repeat this process.

There are several points in this process:

  • There may be several more levels of functional tests(such as module tests, integration tests) you need to write between acceptance tests and unit tests depending on how do you organize your tests in your project.
  • Always keep it in mind that doing simplest things to make your tests pass. Apply this rule when you try to pass the unit tests and functional tests.
  • A prerequisite of this approach is a good user story. A good story is a INVEST story which is small and testable. If you don’t have clear acceptance criteria or the story is not testable , you may find it’s difficult to write acceptance tests first. If your story is too big, you may find there are too many acceptance tests you have to make them pass and the story will take you very long time.

Why

This is not the only approach we have. As I said at the beginning, different projects and different people have their favorites. Here are some reasons why I like to use this approach.

TDD zealots like me want to test everything in the software and make everything automatic. We all know the rule “no tests, no code”, because tests represent the requirements. We think by expressing those requirements using tests first will make sure that we have understand the requirements before we start to write production code to meet those requirements. Tests also provide quality assurance for our production code, serving the documentation and ground for refactoring etc.

Things become a little complex and interesting when a story has different kinds of or levels of tests. When you are writing unit tests, you may be already clear about the functionality and interfaces of the class you want to test. But how does the need of this class come? How do you decide we need this class and we will design like this? The answer is you decide to create these classes to meet the functionality described in the story, you are writing unit tests according to your understanding of the functionality described in the story which usually comes by your talking with business analysts.

At this time we can say without the requirements of the story(functionality described in the story and usually represented as acceptance criteria), we are not clear how will our code look like and what unit tests to write. Since you write unit tests to test a unit piece of functionality(usually an public interface of an class) which is again determined by the functionality represented by the story, since you have to understand functionality first to write unit tests, why not first write functional tests to represent the requirement, see it fail then find out the solution and write the corresponding unit tests.

So starting from writing acceptance tests will make sure you have understood the requirements of the story which will help you to choose a simple solution to fulfill the requirement represented by the acceptance tests, then write unit tests to based on your clear understanding of each classes’ functionality in the solution and use simple code to make them pass. This is a very natural TDD approach.

How?

As we said above, the first step is to turn acceptance criteria into tests, so one of the most important factors for us is to find tools that support us to achieve that goal. Functional testing tools are actually a very important part in our process. Given that there are already many unit testing tools available for nearly every programming language, a suitable functional testing tool seems more important for us.

While unit testing tools can be categorized by programming language, we can examine those functional testing tools by domains of problems. Due to differences of technical implementation nature of web application, windows desktop application and java GUI application etc., there are different functional testing tools for them.

For web applications, functional testing tools such as Selenium, Watir(Watin, Watij), Sahi are available. We have Abbot for java GUI applications, NUnitForm for Windows Form applications and Microsoft UIAutomation Framework for a wide range of windows application. More heavy tools like QTP could also be a choice. For other kinds of software such as library, console application we can either use xUnit framework to do functional testing or we can use varies of other approaches to achieve such purposes to some extent.

It seems that functional testing tools for some kinds of applications are more abundant and mature than those for other kinds, which seems make implementing the practice in this article easier in some applications than in others. It is probably true, but just remember the rationale behind this approach and once you understand and agree with it and try to apply the rules behind to the greatest possibility, you can always find other variations and substitutes in practice.

By continuously refactoring your testing code, you can actually make your tests easier to understand and more business natural(DSL). Some tools like rbehave and jbehave are already able to allow us to write our acceptance tests in a way more clearly reflecting business value and easier to understand by non-technical people. They help developers to think about the problem and their code from a perspective which focus more on business value of a story.

Pros and Cons

Pros

  • Writing acceptance tests first makes developers think more about value before starting to code

It’s really hard to write acceptance tests first without clearly understand what are you going to test. Strictly applying the rule of “writing tests first” forces people to think more about business requirement before they start to write any code. This approach encourages developers to understand the business first by either talking to business analysts or customers directly, by this way the misunderstanding of requirement can be reduced to minimum extent and the discrepancy between business people and development team can be decreased.

  • Test automation is easily achievable from the beginning of the project

TDD starting from acceptance tests implies automation of all tests in the project by itself, because it’s impossible for people to get fast feedback without the ability to automatically build the project and run tests. With the support of continuous integration tools such as CruiseControl, regression testing is also easily achievable.

  • Clear and simple design, testable code

As mentioned before, we should clearly understand the business value of the story and the acceptance criteria and then turn them into tests. The rest of our work is just to use simplest code to make those tests pass. If writing unit tests first can help us achieve simple design of each public interface of a class, writing functional tests first can also help us choose simplest design to implement the business value.

  • Tests as documents

One byproduct of TDD is a good set of tests which can serve as documentation of the project. Having a high quality collection of functional tests can help everyone understand the business requirement and the functionality of the application by just going through the functional tests, Meanwhile, unit tests are documents for people to understand the implementation of each class.

There are tools such as Testdox helping us translate our tests which can only be understood by technical people into a format more friendly to non-technical people. Testing frameworks like rspec already have built-in support of this kind of translation, and other frameworks like rbehave and jbehave make the tests themselves represented by business natural language. All of those efforts can make our tests more representative of business value, more self-explaining and more easy to understand.

  • Delay the implementing and design decision

Given the existence of the tests, developers can make implementation and design decisions at the last step, after tests being written.

  • nurture the good habits of programming

Developers should never only focus on coding and technical problems. Good developers should have a sense of considering everything to make what they produced really valuable and with high quality. Agile software development methodology requires developers to be more versatile. Besides having capability of generating high quality code, developers should understand the value of what they are going to do and be able to question the requirement if they are not so valuable. Thinking about our code from the perspective of the value they produced helps us nurture a good habit of simple design. TDD from stories can help us to achieve this.

  • Testing team more effective

With functional tests written by developers and automated, testers can be more effective. Tester can spend most of their time on other testing such as performance testing, exploratory testing and manual testing etc. They can focus their energy on tests that can not achieved by automated tests. They can spend more time on acceptance criteria and providing their testing ideas rather than actually write tests code.

One practical problem is that some testers can not generate very good tests code. This problem become more serious when the tools need more programming thinking and the test suite become huge while the project grows bigger. The problem disappears if developers write most functional tests.

In agile planning, we usually have to estimate how much work a story will take. Developers writing functional tests first can make them more confident and easier to say that a story has been finished if all tests passed, which helps them estimate the work of each story more accurately. That will make the release planning easier.

  • Not mention fast feedback, iterative release

Cons

  • Sometimes it’s not easy

While we are talking about many benefits of this TDD style, we should realize that sometimes it is difficult. For people who have been used to it, it is very natural and happy to do this, but it might be difficult to be accepted by other people. For most people, TDD itself is a mind shift. They might have been used to writing code first and testing and fixing the problems. The common questions from them are such that how can I write tests without the code that I am going to test. Doing TDD and using this approach need people to understand the problem first, then you can write tests to represent the requirements. It’s not easy at the beginning, but it’s a good practice and worth spending time on it. We should make ourselves disciplined and think about the software from value perspective. Pairing with some experienced people can also help us quickly be accustomed to this approach.

TDD from stories need some prerequisites. Firstly we need good stories, good stories are independent, valuable, estimable, small and testable stories. Good stories make developers easy to understand the business value and raise good questions. They are small and testable so that we can finish them with writing tests first in reasonable time. Secondly the acceptance criteria of each story should be clear, so that developers can easily turn those into tests. A good story is the corner stone of a successful project.

  • Difficult in some areas like cpp, game, restricted by the availability of tool set

We have said that one important factor for this approach is having a good functional testing tool, which is not always the case. In practice sometimes it’s very difficult to find a good functional testing tool for your application because of the programming language you use, the target platform of your application or the framework your application depending on. For example, there are not very handy tools for testing windows desktop application. QTP is a good tool, but it’s too heavy for TDD. Another example is game. It is also very difficult to implement TDD in some languages such as C, Cpp.

Tools are evolving. Several years ago it’s also very difficult to do functional testing for web application, but nowadays there are a bunch of tools for web application functional testing. The requirement will drive people to make better tools.

  • Build will get slow if not being careful because functional tests number increases

By definition, functional tests test the real functionality of an software. Usually we need to hit the databases, transfer data through the network, writing and reading files etc. in our functional tests. One common problem is while a project becomes bigger, the time spent on running all those functional tests will be very long. That will make us unable to get fast feedback from functional tests and decrease the productivity of the team. It will make developers unwilling to run those tests and write functional tests.

Sometimes we have to face this reality, but in most situations we can find many solutions to solve this problem or at least we can bypass it. For example, we can write and run functional tests belong to the story that we are working on during development, and then we run all tests before checking in our code. In some cases we may divide tests into smaller groups and run them parallel. We may also be able to reorganize our tests and use stubs and mocks to test our application without talking to some external systems.

A sample

Let’s use some sample code to demonstrate the whole idea and process. Suppose that we are working on a web application, we have a story about login functionality, which is:

 As a user, I want to login to the website, so that I can use
registered user specific functionality.  

For this story, we have those acceptance criteria:

* Happy path: successfully login
Given: user go to the login page
When: user input correct username and password and submit
Then: user logged in with a successful message

* Username or password missing
Given: user goto the login page
When: user does not input username or password and submit
Then: user should see an error message with “username or password missing”

* Username or password incorrect
Given: user goto the login page
When: user input wrong username or password and submit
Then: user should see an error message with “username or password is not correct”

Suppose we use Ruby on Rails to make this web application, with the help of selenium, we can easily write acceptance tests for this story:

<src lang="ruby">
Story "Login", %(
        As a user,
        I want to sign in the website,
        So that I can use registered user specific functionality) do

        @selenium = Selenium::SeleniumDriver.new("localhost", 4444, "*iexplore", "http://localhost", 10000);

      Scenario "user successfully login" do
        Given "correct username and password" do
            @selenium.start
            @selenium.open "http://localhost/users/login"
            @selenium.type "username", "someone"
            @selenium.type "password", "password"
        end

        When "login" do
            @selenium.click "submit"
        end

        Then "user logged in successfully" do
            @selenium.is_text_present "Welcome, someone!"
            @selenium.stop
        end

    end

    other scenarios...

end
</src>

Here we use rbehave, a framework for expressing business acceptance criteria using ruby code. After we got this tests, we can run them and see the failure. Now we can think about how to make this test pass. At this point, we probably need to create a UsersController and an action called login, and have User model. Once we made that decision, we can start to write tests for them:

<src lang="ruby">
def test_registered_user_should_able_to_sign_in_and_redirect_to_home_as_default
    post :login, :name => "someone", :password => "password"
    assert_equal "Welcome, someone!", flash[:notice]
end
</src>

Here we test that the login action of Users control should perform the login and then set a flash notice, then we will come up with a controller action:

<src lang="ruby">
def signin
    user = User.authenticate(params[:name], params[:password])
    flash[:notice] = "Welcome, " + user.nickname + "!"
    redirect_to :controller => "home"
end
</src>

Obviously we delegate the authentication to the User model, so we write the tests for it:

<src lang="ruby">
def should_be_able_to_get_authenticated_user
    assert_equal users(:someone), User.authenticate("some user", "user's password")
    ...
end
</src>

After this we can easily write some simple code to implement this authenticate method. We don’t have to worry about too much exception handling because we are currently just focusing on the happy path. After we write more tests, we will make our code more robust gradually.

So far I have presented the process of implementing a successful scenario of a simple story. Notice that we solved a problem by dividing them into many vertical slices(here are different scenarios), every time we finish one slice, write the tests first and write the simplest code to make them pass. In this way we are very confident at each step, and we can clearly see the value we are creating.

Variations

  • Lack of tools support

Sometimes because of lack of testing tools, we can not write full scenario functional tests for the application. Under this situation we still want to achieve this goal to the greatest extent, so we write functional tests for modules instead of whole application(end to end). For example, if we can not find a very handy functional test tool for Windows application, we may organize our code to some pattern like passive view, so that without performing actions through the view, we still can write functional tests for our application (maybe through testing the controller). Another example is in a C/S rich client application, we can test the server module’s functionality to guarantee that the server works as we expected.

  • Dev unit test -> add functional tests
  • Extract writing functional tests from dev to tester

It is very attractive for QAs to write functional tests and for Developers to write unit tests and let them work parallel. It seems the productivity of the whole team will be greatest. Once the acceptance criteria has been clarified, dev can write unit tests, and tester may write functional tests. Ideally it’s true, but it’s difficult practically. The following are some potential problems related to this approach:

  • Sometimes dev and testers can work parallel, which may increase productivity(but again it’s dubious, because this may need more communication and iteration between testers and devs).
  • Depending on the tools the QA uses, sometimes it requires QA to have more advanced programming skill to write and maintain functional test suite. If QA does not have that level of skill, it’s difficult for QAs to catch up with devs to write functional tests for new developed functionalities. If developers deliver the stories very fast, QAs’ job will be more difficult.

If these problems happen, there are some ways to mitigate it. One way is to let developer write and maintain some (not all) functional tests (happy path tests, for example), integrate them into developers’ build and make sure them pass before developers’ commit.


Tweak Emacs

After I used Emacs, Eclipse and Intellij Idea for a period of time. I gradually found some of their best features that we use everyday. I found Intellij idea is quite good, but it’s heavy and not very suitable for editing some kind of files(ruby, for instance). I like Emacs but when I use it, I found some features and key bindings are not well designed to be very efficient (at least for me). So I decide to add those features and change the key bindings to make it more comfortable to use it.

Here are some features that I think a good editor or IDE should have:

  • Move(word, sentence, sexp, paragraph, page).
  • Transpose(line, that is to move one line upward or downward)
  • Mark (word, string, sexp, paragraph, page, whole document, C-w in idea)
  • Delete(word, space, empty lines, one line, zap to char)
  • Insert(split line, open line, duplicate one line)
  • Case change(word, region)
  • Live template. I like to invoke the template by pressing <tab>
  • Auto completion. Auto completion was bound to C-SPC in idea, alt-enter in eclipse. There should be only one key binding for all kinds of auto completion. If there are multiple choices, there should be some easy way to choose what you really want.
    • auto completion while typing. like pabbrev
    • for pairing tags like xml tags, finish typing the first part should invoke the insertion of the second part and the cursor should stay between them in the end.
    • library auto completion
    • tag auto completion
  • Comment and uncomment should use same key binding. Editor should be able to check if some text has been commented and execute the corresponding command. Generally, if a selected region containing some lines that are not commented, the command should comment the region, otherwise uncomment it. C-/ is a good key binding for this.
  • Navigation:
    • Go back and forward. In idea, they are bound to C-M-Left and C-M-Right.
    • Go to declaration. In emacs, the tag can support this to some extent. Need investigation.
    • Last edit position which is pretty much like the above one.
  • Document reference support
  • Spell checking
  • Tab bar. The tabbar design can referred to idea.
  • Should provide an unobtrusive way to popup a window displaying a directory structure or type hierarchy etc.

IDE feature:

  • Support unit test running, comipling and run the application etc.
  • Refactoring
  • More sophiscated ide can support intentional programming.

Some thoughts

  • Moving is a very common operation. Usually people use mouse to move their cursor. But they can be done using pure keyboard too, emacs does that perfectly. But those moving key bindings should be simple and not be scattered, which means you don’t need to press many keys to move and the switch from moving by word to by sentence or sexp should be very comfortable.
  • C-w style marking implemented by IDEA is very nice.
  • Delete extra space or empty lines are nice features. They can also be done while formatting the code. And deleting word, sexp or sentence where the cursor is in are also very useful.

Frequent operations should be bound to key bindings that are easy to press. such as copy, paste, cut, delete one line. The key binding should also consider using mouse. Actually, mouse can be very useful. I think the key to be efficient is not that you don’t have to move your hands outside the keyboard to finish all tasks but be able to do what you want by very simple and few steps. If you can use mouse to select a region and your left hand can easily and comfortably perform cut and paste operation, I think that’s better than using C-N or C-P like key combination to select a region.

The design of idea is intentional programming. You do not have to browse the file structure to select the file you want to edit. You can navigate easily while you are thinking, to finish your job you don’t need many mouse clicks and key pressing, that’s partly why people says ecplise is not as good as idea. You just have to click more and press more to finish a job than using idea and Eclipse don’t support you to program smoothly.

The same idea can be applied to emacs. Do it simple. Frequent operations should be able to be done by very simple steps. More unusual operations can be bound to more complex key sequence. For example, moving by sexps in lisps should be simple to do. Moreover, if you don’t want to let your hands leave the keyboard, most moving operations should not need more than 1 key pressing to do it. Like moving by paragraph, the default key binding is C-S-Up or Down, which is good, while you are holding the C and S, you can move the cursor by paragraph as many times as you wish by just pressing up or down. Some other operations like compile, open html file in browser etc. are not very frequently used, so they can be bound to key sequence over 2 key pressing( such as C-c C-v ).

Don’t complain that emacs doesn’t support some ide feature like file structure pane very well. Anyway, we should provide them ,but the important thing is to make programming in emacs don’t need you to use them very often, like in idea.

How to make emacs more comfortable to use

  • (done)Swap the caps lock and left control on your dell d610 keyboard, just because the design is bad.
  • Kill-buffer command (C-x k) is used very often(at least for me), which can be bound to a more convenient key binding.
  • Idea has a nice feature(maybe controversial): when it loses the focus, it saves all open files in its window.
  • (done)When a region is selected, pressing Backspace or DEL Should delete the region
  • Speedbar should be better too. Refer to the design of textmate.
  • Scroll bar. the vertical bar is not necessary, but there should be some convenient way to scroll horizontally.
  • Different major modes should be able to have different set of live templates.
  • Tabbar
  • Auto completion
  • Navigation
  • Unit test support
  • (done)Move one line upward or downward
  • (done)Duplicate one line
  • (done)Delete one line
  • (done)Comment and uncomment a region or one line

Here are some key bindings that easy to use:

  • <tab>, TAB
  • C-, M-, C-M-
  • C-c … C-x … C-h …

(M here means ‘alt’ key)

some features that are often forgotten in emacs:

  • keyboard macro
  • register
  • bookmark
  • rectangle

The following are some code of those above works that I have marked (done):

<src lang="elisp">
(defun tweakemacs-delete-region-or-char ()
  "Delete a region or a single character."
  (interactive)
  (if mark-active
      (kill-region (region-beginning) (region-end))
    (delete-char 1)))
(global-set-key (kbd "C-d") 'tweakemacs-delete-region-or-char)

(defun tweakemacs-backward-delete-region-or-char ()
  "Backward delete a region or a single character."
  (interactive)
  (if mark-active
      (kill-region (region-beginning) (region-end))
    (backward-delete-char 1)))
(global-set-key [backspace] 'tweakemacs-backward-delete-region-or-char)

(defun tweakemacs-duplicate-one-line ()
  "Duplicate the current line. There is a little bug: when current line is the last line of the buffer, this will not work as expected. Anyway, that's ok for me."
  (interactive)
  (let ((start (progn (beginning-of-line) (point)))
	(end (progn (next-line 1) (beginning-of-line) (point))))
    (insert-buffer-substring (current-buffer) start end)
    (forward-line -1)))
(global-set-key (kbd "C-=") 'tweakemacs-duplicate-one-line)

(defun tweakemacs-delete-one-line ()
  "Delete current line."
  (interactive)
  (beginning-of-line)
  (kill-line)
  (kill-line))
(global-set-key "M-o" 'tweakemacs-delete-one-line)

(defun tweakemacs-move-one-line-downward ()
  "Move current line downward once."
  (interactive)
  (forward-line)
  (transpose-lines 1)
  (forward-line -1))
(global-set-key [C-M-down] 'tweakemacs-move-one-line-downward)

(defun tweakemacs-move-one-line-upward ()
  "Move current line upward once."
  (interactive)
  (transpose-lines 1)
  (forward-line -2))
(global-set-key [C-M-up] 'tweakemacs-move-one-line-upward)

;; There is a bug in the uncomment-region. When you select
;; the last line of the buffer and if that is a comment,
;; uncomment-region to that region will throw an error: Can't find the comment end.
;; Because I use uncomment-region here, so this command also has this bug.
(defun tweakemacs-comment-dwim-region-or-one-line (arg)
  "When a region exists, execute comment-dwim, or if comment or uncomment the current line according to if the current line is a comment."
  (interactive "*P")
  (if mark-active
      (comment-dwim arg)
    (save-excursion
      (let ((has-comment? (progn (beginning-of-line) (looking-at (concat "\s-*" (regexp-quote comment-start))))))
	(push-mark (point) nil t)
	(end-of-line)
	(if has-comment?
	    (uncomment-region (mark) (point))
	  (comment-region (mark) (point)))))))
(global-set-key (kbd "C-/") 'tweakemacs-comment-dwim-region-or-one-line)

</src>