Chàng kỹ sư trẻ

, , ,

Tác giả: Robert C. Martin (Uncle Bob) | Nguồn: http://blog.cleancoder.com

Tôi muốn trở thành một Kỹ sư Phần mềm.

Đó là một mục tiêu rất tuyệt cho một nhà phát triển phần mềm trẻ. 

Tôi muốn lãnh đạo một nhóm và có thể đưa ra những quyết định quan trọng về cơ sở dữ liệu, framework, máy chủ web, và tất cả mọi thứ.

Ồ, tốt! nhưng nghe nó không giống là bạn muốn trở thành Kỹ sư Phần mềm lắm.

Tất nhiên là tôi có, tôi muốn trở thành một người có thể đưa ra những quyết định quan trọng.

Ổn thôi, nhưng bạn không nói được những quyết định quan trọng ấy. Bạn toàn nói về những điều không liên quan.

Ý của bạn là gì? Cơ sở dữ liệu không phải là quyết định quan trọng ư? Bạn có biết chúng tôi đã chi bao nhiêu tiền cho chúng không?

Có lẽ là rất nhiều. Nhưng không, cơ sở dữ liệu không phải là một trong những quyết định quan trọng nhất. 

Làm thế nào bạn nói như vậy được? Cơ sở dữ liệu như là trái tim của hệ thống! Nó là nơi mà dữ liệu được tổ chức, sắp xếp, …., và được truy cập. Không có nó, thì cũng không có hệ thống.

Cơ sở dữ liệu chỉ đơn thần là một công cụ cho Nhập Xuất (IO). Nó tồn tại để cung cấp một số công cụ hữu ích cho việc phân loại, truy vấn, và báo cáo nhưng chúng chỉ là hỗ trợ cho kiến trúc hệ thống.

Hỗ trợ? Thật là nực cười.

Đúng vậy, là hỗ trợ. Những quy tắc nghiệp vụ của hệ thống của bạn có thể sử dụng những công cụ này, nhưng chúng không phải là quá quan trọng cho những quy tắc đó. Nếu như bắt buộc thì bạn có thể thay thế những công cụ này bằng các công cụ khác, nhưng những quy tắc nghiệp vụ của bạn thì vẫn giữ nguyên.

Vâng, nhưng tôi phải viết lại tất cả chúng từ khi những quy tắc nghiệp vụ đều sử dụng những công cụ trong cơ sở dữ liệu gốc.

Vâng! Đó là vấn đề của bạn. 

Ý bạn là gì? 

Vấn đề của bạn là bạn tin vào việc những quy tắc nghiệp vụ phụ thuộc vào những công cụ của cơ sở dữ liệu. Chúng không quan trọng. Hay ít nhất chúng không quan trọng nếu bạn cung cấp một kiến trúc tốt.

Đó là chuyện thật điên rồ. Làm thế nào bạn tạo ra được những quy tắc nghiệp vụ mà không sử dụng những công cụ bắt buộc.

Tôi không nói là chúng không sử dụng những công cụ của cơ sở dữ liệu, tôi nói chúng không nên phụ thuộc quá vào những công cụ mà thôi. Những quy tắc nghiệp vụ không nên biết quá rõ về những dữ liệu chi tiết mà bạn đang sử dụng.

Làm thế nào bạn có được những quy tắc nghiệp vụ để sử dụng những công cụ mà không biết chút nào về chúng?

Bạn hãy đảo ngược sự phụ thụ thuộc đi. Bạn có cơ sở dữ liệu phụ thuộc vào những quy tắc nghiệp vụ. Bạn chắc chắn rằng những quy tắc nghiệp vụ không phụ thuộc vào cơ sở dữ liệu. 

Bạn đang nói những điều vô lý đấy.

Ngược lại là khác,  tôi đang nói trên quan điểm của một Kỹsư Phần mềm. Đây chính là Nguyên lý Đảo ngược Phụ thuộc (Dependency Inversion Principle, một trong các nguyên lý của SOLID). Những chính sách cấp độ thấp (low level policy) nên phụ thuộc vào những chính sách cấp độ cao (high level policy).

Lại càng vô lý! Những high level policy (tôi tin ý của bạn là những quy tắc nghiệp vụ) ra lệnh cho những low level policy (tôi đoán bạn muốn chỉ cơ sở dữ liệu). Nên những high level policy phụ thuộc những low level policy giống như những người gọi phụ thuộc vào những người nghe. Tất cả mọi người đều biết điều này!

Tại thời điểm chạy (runtime) thì điều này đúng. Nhưng lúc biên dịch (compile time) chúng ta muốn những sự phụ thuộc được thay đổi. Mã nguồn của những high level policy không nên đề cập tới mã nguồn của những low level policy.

Ồ thôi nào! Bạn không thể gọi điều gì đó mà không đề cập tới nó được.

Dĩ nhiên là bạn có thể. Đó chính là ý nghĩa của Hướng đối tượng (Object Orientation).

Object Orientation là tạo ra những mô hình của thế giới thực, nó là sự kết hợp giữa dữ liệu và hàm vào những đối tượng gắn kết. Nó là về sự sắp xếp các dòng mã thành một cấu trúc trực quan.

Đó là những gì người ta nói với bạn ư?

Mọi người đều biết về nó. Nó chắc chắn đúng.

Không chắc. Không chắc. Chưa hết, việc sử dụng những nguyên tắc của object-orientation bạn có thể thật sự gọi điều gì đó mà không cần đề cập tới nó.

Làm cách nào?

Bạn biết rằng những đối tượng thiết kế hướng đối tượng gửi những thông điệp cho nhau?

Vâng. Dĩ nhiên.

Và bạn biết rằng bên gửi thông điệp không biết về kiểu của bên nhận.

Nó còn phụ thuộc vào ngôn ngữ lập trình nào. Trong Java bên gửi ít nhất là biết kiểu của bên nhận. Trong Ruby bên gửi ít nhất biết được rằng bên nhận có thể giải mã được thông điệp đang gửi.

Đúng. Nhưng những trường hợp khác, bên gửi không biết chính xác kiểu của bên nhận.

Vâng. chắc chắn.

Vì thế bên gửi có thể yêu cầu một hàm chạy ở bên nhận, mà không cần đề cập chính xác kiểu của bên nhận.

Vâng, đúng. Tôi biết điều đó. Nhưng bên gửi vẫn phụ thuộc vào bên nhận.

Tại thời điểm runtime thì đúng. Nhưng nó không đúng ở compile time. Mã nguồn của bên gửi không đề cập, hay phụ thuộc mã nguồn bên nhận. Trong thực tế, mã nguồn của bên nhận phụ thuộc vào mã nguồn bên gửi.

Này! Bên gửi vẫn phụ thuộc vào lớp mà nó đang gửi đến.

Có thể một VD về mã có thể làm rõ điều này hơn. Tôi sẽ chứng minh điều này trong Java. Đầu tiên là gói sender:

package sender;

public class Sender {
  private Receiver receiver;

  public Sender(Receiver r) {
    receiver = r;
  }

  public void doSomething() {
    receiver.receiveThis();
  }

  public interface Receiver {
    void receiveThis();
  }
}

Tiếp đến là gói receiver.

package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {
  public void receiveThis() {
    //do something interesting.
  }
}


Chú ý rằng gói sender phụ thuộc vào gói receiver. Cũng lưu ý rằng lớp SpecificReceicer phụ thuộc vào lớp Sender. Hãy để ý gói sender cũng không biết chút nào về gói receiver.

Vâng, nhưng bạn đã nói dối. Bạn đặt giao diện (interface) của bên nhận trong nhóm bên gửi.

Bạn đã bắt đầu hiều rồi đấy.

Hiểu gì?

Tất nhiên, những nguyên tắc của kiến trúc. Lớp Sender có những giao diện mà lớp Receiver phải triển khai.

Ồ, thế có nghĩa là tôi phải sử dụng các nested-class (lớp lồng nhau)…

Nested classes chỉ là một phương tiện để đạt được điều này. Có những cách khác nữa.

OK, đợi đã. Tất cả điều này phải làm gì với những cơ sở dữ liệu. Đó là cái chúng ta đề cập ban đầu.

Hãy xem thêm những dòng mã khác. Đầu tiên, một quy tắc nghiệp vụ đơn giản:

package businessRules;

import entities.Something;

public class BusinessRule {
  private BusinessRuleGateway gateway;

  public BusinessRule(BusinessRuleGateway gateway) {
    this.gateway = gateway;
  }

  public void execute(String id) {
    gateway.startTransaction();
    Something thing = gateway.getSomething(id);
    thing.makeChanges();
    gateway.saveSomething(thing);
    gateway.endTransaction();
  }
}

 

Quy tắc nghiệp vụ đó không làm được gì nhiều cả.

Nó chỉ là một ví dụ. Bạn sẽ có nhiều lớp khác như vậy triển khai thực hiện rất nhiều những quy tắc nghiệp vụ khác nhau.

OK, Vậy thì Gateway có vấn đề gì? 

Nó phụ thuộc vào tất cả những phương thức truy cập dữ liệu bởi quy tắc nghiệp vụ. Nó được cài đặt như sau:

package businessRules;

import entities.Something;

public interface BusinessRuleGateway {
  Something getSomething(String id);
  void startTransaction();
  void saveSomething(Something thing);
  void endTransaction();
}


Chú ý rằng nó nằm trong gói businessRules.

Vâng OK. Còn lớp Something là gì?

Đó là thể hiện một đối tượng nghiệp vụ đơn giản. Tôi để nó vào một gói có tên là entities. 

package entities;

public class Something {
  public void makeChanges() {
    //…
  }
}

 

Và cuối cùng là triển khai giao diện BusinessRuleGateway. Lớp triển khai biết về cơ sở dữ liệu thật sự:

package database;

import businessRules.BusinessRuleGateway;
import entities.Something;

public class MySqlBusinessRuleGateway implements BusinessRuleGateway {
  public Something getSomething(String id) {
    // use MySql to get a thing.
  }

  public void startTransaction() {
    // start MySql transaction
  }

  public void saveSomething(Something thing) {
    // save thing in MySql
  }

  public void endTransaction() {
    // end MySql transaction
  }
}


Hãy để ý những quy tắc nghiệp vụ gọi cơ sở dữ liệu lúc runtimes, nhưng tại thời điểm compile time  nó là gói database được đề cập và phụ thuộc vào gói businessRules.

OK, ok, tôi nghĩ mình hiểu rồi. Bạn chỉ đang sử dụng sự đang hình để dấu đi việc chạy cơ sở dữ liệu từ những quy tắc nghiệp vụ. Nhưng bạn vẫn phải có giao diện mà cung cấp tất cả công cụ cơ sở dữ liệu cho những quy tắc nghiệp vụ.

Không, không phải  tất cả. Chúng ta không có gắng cung cấp tất cả công cụ cơ sở dữ liệu cho những quy tắc nghiệp vụ. Ngoài ra, chúng ta có những quy tắc nghiệp vụ tạo những giao diện chỉ cho những gì chúng ta cần. Việc triển khai những giao diện này có thể gọi những công cụ phù hợp.

Vâng, nhưng nếu tất cả những quy tắc nghiệp vụ cần tất cả những công cụ, sau đó bạn chỉ phải để tất cả các công cụ tại giao diện gateway.

À. Tôi thấy là bạn vẫn chưa hiểu rõ lắm.

Hiểu cái gì? Tôi đã hoàn toàn hiểu nó rồi.

Mỗi quy tắc nghiệp vụ định nghĩa một giao diện chỉ cho dữ liệu truy cập dễ dàng những thứ mà nó cần.

Đợi chút. Cái gì vậy?

Điều này được gọi là Nguyên lý Phân tách Giao diện (Interface Segregation Principle). Mỗi lớp quy tắc nghiệp vụ sẽ chỉ sử dụng vài cơ sở của cơ sở dữ liệu. Và cho nên mỗi quy tắc nghiệp vụ cung cấp một giao diện mà cho nó chỉ được truy cập vào những cơ sở đó.

Nhưng điều đó có nghĩa là bạn sẽ có rất nhiều giao diện, và nhiều những lớp triển khai nhỏ để gọi những lớp cơ sở dữ liệu khác.

À, tốt. Bạn bắt đầu nắm được rồi đấy.

Nhưng nó rối, và mất thời gian! Tại sao tôi nên làm vậy?

Bạn nên làm vì nó gọn và tiết kiệm thời gian.

Ồ thôi nào. Đó chỉ là một đống mã cho lợi cho mã.

Ngược lại, có những quyết định kiến trúc quan trọng cho phép bạn  trì hoãn những quyết định không liên quan.

Bạn nói vậy là có ý gì?

Hãy nhớ rằng bạn đã bắt đầu với việc bạn nói mình muốn trở thành Kỹ sư Phần mềm? Và bạn muốn đưa ra tất cả những quyết định thật sự quan trọng?

Vâng, đó là điều tôi muốn.

Trong những quyết định đấy bạn muốn quyết định về cơ sở dữ liệu, về máy chủ web, về framework.

Vâng, và bạn đã nói chúng chẳng phải là những quyết định quan trọng. Bạn nói chúng không liên quan.

Đúng vậy, chúng không liên quan. Những quyết định quan trọng mà một Kỹ sư Phần mềm phải chỉ ra những điều mà KHÔNG cho phép bạn được đưa ra quyết định về cơ sở dữ liệu, về máy chủ web, và về framework.

Nhưng bạn phải đưa ra những quyết định trước!

Không, không cần. Bạn thật sự muốn được cho phép thực hiện chúng sau cùng trong chu kỳ phát triển – khi mà bạn có nhiều thông tin hơn.

Vấn đề là những kỹ sư mà quyết định sớm cơ sở dữ liệu, và sau đó nhận ra rằng những tập tin là đã đủ.

Khổ là những kỹ sư mà quyết định sớm dựa vào máy chủ web, chỉ để nhận ra rằng cả nhóm thật sự cần là một giao diện socket đơn giản.

Đáng buồn là những nhóm kỹ sư ép buộc một framework sẽ sớm phụ thuộc vào chúng, chỉ nhận ra rằng framework cung cấp những thứ mà họ không cần và thêm những ràng buộc mà họ không thể nào chấp nhận.

May mắn là nhóm các kỹ sư được cung cấp những phương pháp mà tất cả các quyết định có thể bị trì hoãn cho đến khi có đủ thông tin để ra quyết định.

Rất vui là nhóm có các kiến trúc sư đã cô lập chúng với những thiết bị OI thiếu tài nguyên và chậm, và những framework mà họ có thể tạo các môi trường kiểm thử nhanh và gọn nhẹ.

Cũng may mắn là nhóm có đội kiến trúc sư quan tâm đến những gì thật quan trọng, và trì hoãn lại những điều chưa thật sự quan trọng.

Vô lý. Tôi không hiểu gì cả.

Vâng, có thể là bạn sẽ mất cả chục năm hoặc lâu hơn… Nếu lúc đó bạn vẫn không làm công việc quản lý.