寫好軟體的訣竅

發表於2013-05-08

  程式設計師身上的一個好笑的事情是,我們在畢生的職業生涯中都相信:我們的工作是告訴計算機如何去做。

  真實情況

  真實情況是,計算機能正確的按照命令去執行。無論你寫的是“Hello World”,還是用無人飛機去殺死一個人。計算機都能精確的按照你的命令去做。

  可我們的工作,我們的真正工作是:告訴程式設計師和我們自己:我們讓計算機做什麼了。現代的軟體程式設計思想就是結構化的、清楚的描述計算機將要執行的任務。

  事實上,計算機並不去閱讀你在程式裡寫了什麼,而人會。計算機把程式設計師寫的程式碼編譯成位元組位元,真正會去看你寫的是什麼的只有人類。

  寫軟體要像講故事

  如果你對你的工作和你寫的程式碼的行為有了新的認識,你會馬上很清楚的發現,程式設計工作更像講故事。

  想一想。你是如何知道一個人講故事沒人愛聽的?這很簡單,他老跑題,他老是糾結在不重要的細節上,他老是在故事場景中挑來跳去,等等。你立刻能知道故事被他講爛了。

  雖然在最後你能明白故事裡發生了什麼,你甚至能複述它,但你會喜歡這樣的故事嗎?你會有興趣轉述給別人或豐富故事內容嗎?

  相同的事情也發生在軟體開發中。如果你的程式碼寫的含糊不清,亂七八糟,沒有人會願意欣賞它。沒有人會願意看它第二次。並且你是第一個受它折磨的人。

  訣竅

  那麼,現在你想要知道這個簡單的祕訣,不是嗎?下面就是

程式碼裡的干擾因素越少越好

  注意,我不是在討論明晰的程式碼vs隱晦的程式碼,不是在討論約定優先,不是在討論後設資料程式設計有害或其它類似的東西。

  寫出好的軟體的訣竅是程式碼裡只寫那些能讓你的程式碼講出的故事更有意義的內容。如果它能讓你的程式碼更清楚,那就這樣寫它。如果這個東西對故事沒有任何意義,那就扔了它。扔了它能讓故事更好。如果程式碼耦合模組不清,就用後設資料程式設計和約定。

  例子

  有一些經典的例子可以證明這一點。比如,描述一篇帖子和它的作者的關係。

class Post < ActiveRecord::Base
  belongs_to :author, class_name: 'User', foreign_key: :authored_by
end

看見了沒?所有關於類名,外來鍵的資訊都是干擾。去掉它們。

class Post < ActiveRecord::Base
  belongs_to :user
end

  第二版中沒有好聽的“作者”字眼,但卻是更優的,因為它直奔主題,用最簡短的語句告訴所有你想知道的。

  另外一個例子,說一個類需要關聯那些建立/修改它的資訊的使用者

class Setting < ActiveRecord::Base
  belongs_to :creator
  belongs_to :editor

  attr_accessor :editing_user

  before_create :set_creator
  before_update :set_editor

private

  def set_creator
    self.creator = @editing_user
  end

  def set_editor
    self.editor = @editing_user
  end

end

  干擾,所有的這些回撥和attr_acessors都是干擾,都是垃圾資訊,沒有任何價值體現在你想完成的任務中。更簡潔更好的方法是下面這樣寫:

class Setting < ActiveRecord::Base
  belongs_to :creator
  belongs_to :editor

  def editing_user=(user)
    if new_record?
      self.creator = user
    else
      self.editor = user
    end
  end
end

  你可以看到它精煉的告訴了我們發生了什麼。這段程式碼說,這個類有一個記錄建立者,一個編輯者,我們用editing_user賦給它們值。沒有回撥干擾。沒有幾個private方法的無用資訊。

  一個更經典的例子。在controller裡管理資料

class PostsController < ApplicationController
  def create
    if params[:post][:text].present?
      if params[:post][:text] =~ /fuck|cock|shit/
        flash[:error] = "Be nice"
        @achtung = true
      end
    end

    if !@achtung
      @post = Post.new(params[:post])

      if @post.save
        flash[:success] = "Yoo hoo!"
        redirect_to :index
      else
        render :new
      end
    else
      redirect_to :index
    end
  end
end

  所有的這些條件邏輯跟你的controller實際上沒有任何關係。所有的這些邏輯判斷並不屬於controller層負責。當然,你可以這樣做,而其能正常的執行,但這不是好的軟體。

  試試這樣寫

class PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])

    if @post.save
      flash[:success] = "Yoo hoo!"
      redirect_to :index
    else
      render :new
    end
  end
end

class Post < ActiveRecord::Base
  validate :bad_language_check

private

  def bad_language_check
    if text =~ /fuck|shit|cock/
      errors.add(:text, "has some pretty bad language")
    end
  end
end

   現在你的controller能清楚的說明白髮生了什麼。你可以清楚的看明白當記錄可以建立和不能建立時會發生什麼。跟Post類一樣,你可以清楚的理解它在過濾那些不乾淨的文字。而且校驗器有自己單獨的地方。它的實現方式不會影響Post本身。

  結論

  其實很簡單。想寫出好的軟體嗎?別再給機器寫程式碼,從此後為人寫程式碼。

  就這麼簡單。

  英文原文:The Trick To Good Software

相關文章