Sunday, June 27, 2010

Đạo code (Phần 5: Cải tiến)

Phần 5: Cải tiến

Lập Trình Sư nói
"Một chương trình, dù chỉ có 3 dòng code, một ngày nào đó vẫn cần phải được cải tiến"

5.1

Cửa giả dùng hay chẳng phí dầu (không cần phải tra dầu vào bản lề nếu dùng cẩn thận )
Suối nguồn chảy xiết há thành ao (suối chảy xiết thì không thể đọng nước như ao tù)
Lời hay khó lọt kẻ cứng đầu (mịa, chỗ này khó dịch thành thơ quá)
Phần mềm mục nát nếu không launch (phần mềm nếu không chạy (launch) thì sớm muộn gì cũng mục nát thôi )

Đó là những bí mật tuyệt vời.

5.2

Quản đốc hỏi một lập trình phu xem anh ta cần bao lâu để hoàn thành chương trình đang viết. "Sẽ xong trong ngày mai" phu đáp.
"Ta cho rằng điều đó là không thể" quản đốc nói, "Thực sự là ngươi cần bao lâu?"
Phu nghĩ một lúc. Rồi anh ta trả lời: "Tại hạ có một số chức năng muốn viết thêm. Những chức năng này sẽ mất ít nhất hai tuần để hoàn thành".
"Như vậy vẫn là quá ngắn so với dự tính" quản đốc nói "Thôi, chỉ cần ngươi thông báo cho ta khi chương trình hoàn tất là ta mãn nguyện rồi."
Lập trình phu đồng ý.
Vài năm sau quản đốc rửa tay gác phím, trên đường đến dự tiệc chia tay, quản đốc thấy lập trình phu ngủ gục trên bàn phím. Phu đã lập trình suốt đêm.

5.3

Lập trình phu một lần được giao viết một chương trình quản lý tài chính nhỏ.
Anh ta nhận việc và làm như điên trong nhiều ngày. Khi lập trình sư xem kết quả ông nhận thấy phu đã lập trình cả một chương trình soạn thảo, một đống hàm đồ họa tổng quát, một giao diện trí tuệ nhân tạo nhưng chẳng có dòng code nào về tài chính.
Lập trình sư lập tức chất vấn và phê phán, "Đừng nôn nóng" lập trình phu bực mình "tại hạ sẽ lập trình chương trình tài chính sau cùng" gã nói.

5.4

Đời nào dân tốt bỏ mùa?
Đời nào thầy tốt bỏ qua trò nghèo?
Cha tốt con chẳng đói meo
Phu tốt code chẳng mòn theo tháng ngày

Theo bản dịch của anh HiepTH(FTL)

Friday, June 11, 2010

Đạo code (Phần 4: Lập trình)

Phần 4: Lập trình

Lập Trình Sư nói:
"Chương trình tốt tự tạo ra thiên đường cho mình, chương trình tồi tự tạo ra địa ngục giam mình"

4.1

Chương trình cần hết sức gọn nhẹ, nên được cấu tạo từ nhiều hàm liên kết như chuỗi ngọc vậy. Tư tưởng và mục đích của chương trình phải liền mạch, không ngắt quãng. Mạch này không nên quá dài, không nên quá ngắn, không nên có biến thừa, không nên có vòng lặp có thể giản lược, không nên thiếu cấu trúc, không nên quá cứng nhắc.
Chương trình cần tuân theo "Luật hạn chế shock". Luật đó là gì? Đơn giản là khi trả lời người sử dụng, chương trình cần hạn chế tối đa việc gây bất ngờ cho họ.
Một chương trình dù phức tạp đến đâu cũng nên hoạt động như một đơn vị độc lập. Chương trình nên chú trọng đến logic bên trong hơn là giao diện bên ngoài.

* * *

Một chương trình không đáp ứng được những yêu cầu trên sẽ vô cùng lộn xộn. Cách duy nhất để sửa chữa là viết lại chương trình.

4.2

Lập trình phu hỏi Lập Trình Sư: "Thưa, chương trình con viết lúc chạy lúc không mặc dù con đã tuân theo quy tắc lập trình. Con cảm thấy vô cùng bế tắc. Đâu là nguyên nhân thảm cảnh này?"
Lập Trình Sư đáp: "Ngươi bối rối vì ngươi chưa ngộ Đạo. Chỉ có kẻ ngốc mới luôn đòi hỏi một hành động dựa trên lý trí từ phía con người. Vậy thì tại sao ngươi lại đòi hỏi điều đó từ thứ mà con người tạo ra? Máy móc chỉ là máy móc, Đạo mới là hoàn hảo. Quy tắc lập trình chỉ là nhất thời, Đạo mới là mãi mãi. Thế nên để được khai sáng, ngươi phải suy ngẫm về Đạo"
"Vậy làm sao để con nhận ra là mình đã được khai sáng?", lập trình phu hỏi.
"Chương trình của ngươi khi đó sẽ chạy trơn tru", Lập Trình Sư đáp.

4.3

Một Lập Trình Sư giảng về bản chất của Đạo cho một đồ đệ. "Đạo nằm trong tất cả các phần mềm dù phần mềm đó thấp kém đến đâu", Lập Trình Sư nói.
"Trong máy tính cầm tay có Đạo chứ?", đồ đệ hỏi.
"Có", Lập Trình Sư đáp.
"Trong trò chơi điện tử có Đạo chứ?", đồ đệ tiếp.
"Tất nhiên, kể cả trong trò chơi điện tử cũng có Đạo", Lập Trình Sư đáp.
"Cả trong hệ điều hành DOS cho máy tính cá nhân nữa chứ?"
Lập trình sư ho một tiếng, người ông khẽ rung lên "Bài giảng hôm nay kết thúc" ông nói.

4.4

Lập trình phu của thái tử viết một phần mềm, ngón tay phu như nhảy múa trên bàn phím. Chương trình khi dịch không có một lỗi và chạy mượt như cơn gió nhẹ.
"Kinh khủng quá" thái tử thốt lên, "Kỹ năng của túc hạ thật hoàn hảo!"
"Kỹ năng ư?", phu rời mắt khỏi màn hình, "Thứ tại hạ tuân theo là Đạo - vượt trên mọi kỹ năng! Khi tại hạ bắt đầu lập trình, tại hạ thấy toàn bộ bài toán trong một khối hỗn độn. Ba năm sau tại hạ không còn thấy khối hỗn độn nữa. Thay vào đó là những hàm con. Nhưng bây giờ tại hạ không còn thấy gì nữa. Toàn bộ bài toán hoàn toàn không tồn tại trong code. Tri thức của tại hạ trở nên nhàn nhã. Tư tưởng của tại hạ không cần kế hoạch, tự do hoạt động theo bản năng. Tóm lại, chương trình của tại hạ có thể tự lập trình. Thực sự thỉnh thoảng nó cũng gặp một số lỗi lớn. Thấy lỗi xuất hiện, tại hạ chậm rãi, lặng lẽ theo dõi. Rồi tại hạ sửa một dòng code và lỗi lớn vừa xuất hiện đã tan biến như làn khói mỏng. Sau đó tại hạ dịch lại chương trình, ngồi yên tận hưởng cảm giác hân hoan chảy trong cơ thể. Tại hạ nhắm mắt một lúc rồi log off".
"Giá như lập trình phu của ta ai cũng khôn ngoan như thế!" thái tử nói.

Wednesday, June 9, 2010

Làm việc với Database trên Android (P4)

Ngoài ra ta cần có một lớp khai báo một số các phương thức utility để dễ dàng làm việc với Database. Bạn hãy xem ví dụ để thấy cụ thể các phương thức này.

package com.tannm.doan.taskmanager.database;

import android.database.Cursor;

public class DataTable {
 // fields
 private DataRow dataRow = null;

 // methods
 public DataTable(DataRow dataRow) {
  this.dataRow = dataRow;
 }

 public Database getUserDb() {
  return dataRow.getUserDb();
 }

 public String getTableName() {
  return dataRow.getTableName();
 }

 public DataRow getDataRow() {
  return dataRow;
 }

 public boolean createTable() {
  if (getUserDb().tableExists(getTableName())) {
   return true;
  } else {
   return getUserDb()
     .execSQL(
       getSqlTableDefinition(getTableName(), dataRow
         .getTableDef()));
  }
 }

 public String getSqlTableDefinition(String sTableName,
   DataField[] vecTableDef) {
  String def = "CREATE TABLE " + sTableName + " (";
  for (int i = 0; i < vecTableDef.length; i++) {
   def += vecTableDef[i].getColumnDefinition();
   if (i < (vecTableDef.length - 1))
    def += ", ";
  }
  def += ")";
  return def;
 }

 public long insertValues() {
  long lRowId = getUserDb().getSQLiteDb().insert(getTableName(), null,
    dataRow.getContentValues());
  return lRowId;
 }

 public long updateValues(long lRowId) {
  String sWhere = String.format("_ID = %d", lRowId);
  long lRowsUpdated = getUserDb().getSQLiteDb().update(getTableName(),
    dataRow.getContentValues(), sWhere, null);
  return lRowsUpdated;
 }

 public long deleteDataRow(long lRowId) {
  String sWhere = String.format("_ID = %d", lRowId);
  long lRowsUpdated = getUserDb().getSQLiteDb().delete(getTableName(),
    sWhere, null);
  return lRowsUpdated;
 }

 public Cursor locateDataRow(long lRowId) {
  final String s = "select * from %s where _ID = %d";
  String sql = String.format(s, getTableName(), lRowId);
  Cursor cr = getUserDb().getSQLiteDb().rawQuery(sql, null);
  // if cursor valid, set first data row as current
  if ((cr != null) && (cr.getCount() > 0))
   cr.moveToFirst();
  return cr;
 }

 public Cursor locateAlarmDataRow(int iType, long lRefID) {
  final String s = "select * from %s where Type = %d and RefID = %d";
  String sql = String.format(s, getTableName(), iType, lRefID);
  Cursor cr = getUserDb().getSQLiteDb().rawQuery(sql, null);
  // if cursor valid, set first data row as current
  if ((cr != null) && (cr.getCount() > 0))
   cr.moveToFirst();
  return cr;
 }

 public Database.Result updateData(boolean bInsertMode, long lEditRowId) {
  Database.Result result = Database.Result.errUnknown;
  if (getUserDb().isOpened()) {
   try {
    dataRow.setValuesForDataRow();
   } catch (Exception e) {
    return Database.Result.errCantSetValuesForDataRow;
   }
   // select update mode
   if (bInsertMode) {
    // insert new data row
    long lRowId = insertValues();
    if (lRowId > 0) {
     result = Database.Result.Success;
    } else {
     result = Database.Result.errCantInsertNewData;
    }
   } else {
    // update existing data row
    long lRowsUpdated = updateValues(lEditRowId);
    if (lRowsUpdated == 1) {
     result = Database.Result.Success;
    } else {
     result = Database.Result.errCantUpdateData;
    }
   }
  } else {
   result = Database.Result.errNoDbAccess;
  }
  return result;
 }

 public Database.Result deleteData(long iRowId) {
  Database.Result result = Database.Result.errUnknown;
  if (getUserDb().isOpened()) {
   if (getUserDb().tableExists(getTableName())) {
    long lRowsDeleted = deleteDataRow(iRowId);
    if (lRowsDeleted == 1) {
     result = Database.Result.Success;
    } else {
     result = Database.Result.errCantDeleteData;
    }
   } else {
    result = Database.Result.errTableNotExists;
   }
  } else {
   result = Database.Result.errNoDbAccess;
  }
  return result;
 }

 public Database.Result getRowDataForEdit(long lRowId) {
  Database.Result result = Database.Result.errUnknown;
  // get requested data row
  Cursor cr = locateDataRow(lRowId);
  if (cr == null) {
   result = Database.Result.errCantGetData;
  } else {
   if (cr.getCount() > 0) {
    if (dataRow.getValuesFromCursor(cr)) {
     try {
      dataRow.getValuesFromDataRow();
     } catch (Exception e) {
      return Database.Result.errCantGetValuesFromDataRow;
     }
     result = Database.Result.Success;
    } else {
     result = Database.Result.errCantGetDataFromTable;
    }
    cr.close();
   } else {
    result = Database.Result.errCantFindData;
   }
  }
  return result;
 }
}

Trên đây là toàn bộ kinh nghiệm làm việc với Database mà tôi học được trong quá trình làm việc thực tế. Mong giúp đỡ những người mới làm việc với SQLite trên Android. Chúc các bạn thành công

Làm việc với Database trên Android (P3)

Tiếp tục tôi sẽ chỉ các bạn tạo một thể hiện của một dòng dữ liệu.
Mỗi một bảng trong SQLite nên tạo một thể hiện kế thừa từ lớp DataRow này.
Một DataRow chứa một số các DataField nên ta khai báo một mảng DataField.
Ngoài ra ta cần phải khai báo một biến trung ContentValues và một thể hiện Database ta đã tạo ở phần 1

package com.tannm.doan.taskmanager.database;

import android.content.ContentValues;
import android.database.Cursor;

public abstract class DataRow {
 // fields
 protected Database userdb = null;
 private DataField[] vecTableDef = null;
 private ContentValues values = new ContentValues();

 // methods
 public DataRow(Database userdb) {
  this.userdb = userdb;
 }

 public Database getUserDb() {
  return userdb;
 }

 public void setTableDefinition(DataField[] vecTableDef) {
  this.vecTableDef = vecTableDef;
  // initialize field parent
  updateDataFieldsParentRow(this);
 }

 public void updateDataFieldsParentRow(DataRow row) {
  for (int i = 0; i < vecTableDef.length; i++)
   vecTableDef[i].setParentRow(row);
 }

 public void copyTableDefinition(DataRow data) {
  setTableDefinition(data.vecTableDef);
 }

 public DataField[] getTableDef() {
  return vecTableDef;
 }

 public boolean validate() {
  return false;
 }

 public void clearContentValues() {
  values.clear();
 }

 public ContentValues getContentValues() {
  return values;
 }

 public void setContentValues(ContentValues values) {
  this.values = values;
  updateDataFieldsParentRow(this);
 }

 public boolean copyContentValues(ContentValues values) {
  this.values = values;
  updateDataFieldsParentRow(this);
  try {
   getValuesFromDataRow();
   return true;
  } catch (Exception e) {
  }
  return false;
 }

 public DataField value(int idx) {
  return vecTableDef[idx];
 }

 public String fieldName(int idx) {
  return vecTableDef[idx].getName();
 }

 public boolean getValuesFromCursor(Cursor cr) {
  if ((cr != null) && (cr.getPosition() != -1)) {
   for (int idx = 0; idx < vecTableDef.length; idx++) {
    DataField field = value(idx);
    // check if null value
    if (cr.isNull(idx)) {
     field.setNull();
    } else {
     final DataField.Type t = field.getType();
     // parse value by type
     if (t == DataField.Type.INT)
      field.set(cr.getLong(idx));
     if (t == DataField.Type.TEXT)
      field.set(cr.getString(idx));
     if (t == DataField.Type.BOOL)
      field.set((cr.getInt(idx) == 1) ? true : false);
    }
   }
   return true;
  }
  return false;
 }

 // sets fields values data (ContentValues values contener) from parent
 // object fields
 public abstract void setValuesForDataRow();

 // gets data from fields values (ContentValues values contener) to parent
 // object fields
 public abstract void getValuesFromDataRow();

 public abstract String getTableName();

}

Thursday, June 3, 2010

Đạo code (Phần 3: Thiết kế)

Lập Trình Sư nói:
"Khi chương trình đang được test đó là thời điểm quá muộn để thay đổi thiết kế."

3.1

Một nam nhân tham dự triển lãm tin học. Mỗi ngày khi vào cửa, gã nói với người cảnh vệ:
"Ta là một đại đạo, nổi danh với nghề trộm cắp như thần. Nói trước với ngươi là ta sẽ không rời triển lãm khi chưa chôm được thứ gì".
Lời nam nhân khiến người cảnh vệ rất đỗi hoang mang, bởi số thiết bị trong triển lãm trị giá đến trăm vạn lượng vàng. Thế nên người cảnh vệ luôn bám sát theo dõi nhất cử nhất động của gã đại đạo. Tuy nhiên gã đại đạo chỉ đi thơ thẩn giữa các gian hàng. Thỉnh thoảng gã cười mỉm một cách khó hiểu.
Khi đại đạo ra khỏi cửa người cảnh vệ liền lôi gã lại để lục soát nhưn chẳng tìm thấy gì.
Hôm sau đại đạo lại đến. Khi đi qua cửa gã thì thầm với cảnh vệ:
"Hôm qua ta đã thoát ra với một đống chiến lợi phẩm, hôm nay có thể sẽ còn nhiều hơn".
Người cảnh vệ lo sốt vó càng theo sát gã đại đạo, nhưng như hôm trước người cảnh vệ chẳng phát hiện được gì.
Ngày cuối cùng, cảnh vệ không kiềm chế được sự tò mò:
"Thưa ngài đạo chích" cảnh vệ nói "Tôi đã hoang mang đến mất ngủ. Hai khai sáng cho tôi, thứ mà ngài lấy trộm là gì vậy?";
Đại đạo mỉm cười, "Ta trộm ý tưởng" hắn nói.

3.2

Một Lập Trình Sư viết chương trình không có cấu trúc. Một lập trình phu học theo cũng viết một chương trình không có cấu trúc. Khi đánh giá kết quả, Lập Trình Sư đã chửi mắng lập trình phu vì viết code không có cấu trúc, ông nói "Tuyệt học của đại cao thủ đâu phải để cho kẻ tầm thường? Ngươi cần phải lĩnh hội được Đạo rồi ngươi mới được phép bỏ qua cấu trúc chương trình".

3.3

Một lập trình phu làm môn khách tại phủ Ngô Vương. Ngô Vương hỏi phu: "Hệ điều hành và phần mềm kế toán, thứ nào dễ thiết kế hơn?"
"Hệ điều hành", phu đáp.
Ngô Vương ngạc nhiên "Rõ ràng một phần mềm kế toán đơn giản hơn nhiều hơn so với một hệ điều hành", ông nói đầy vẻ nghi ngờ.
"Không đúng lắm", phu nói, "khi thiết kế phần mềm kế toán, lập trình phu phải đáp ứng yêu cầu của nhiều người khác nhau: phần mềm phải hoạt động ra sao, báo cáo lên như thế nào, đáp ứng điều kiện gì của luật tô thuế. Ngược lại, hệ điều hành không bị hạn chế vì những ý kiến bên ngoài. Lập trình phu chỉ tìm cách đơn giản nhất để máy thực hiện bài toán. Đó là lý do vì sao hệ điều hành dễ thiết kế".
Ngô Vương mỉm cười gật đầu "Đúng lắm, thế thứ nào dễ debug hơn?"
Phu không đáp.

3.4

Chủ trại lập trình đến gặp Lập Trình Sư và đưa cho ông tài liệu yêu cầu của một phần mềm mới.
Trại chủ hỏi Lập Trình Sư "Việc thiết kế hệ thống sẽ mất bao lâu nếu ta để năm lập trình phu làm?"
"Mất một năm", Lập Trình Sư trả lời ngay.
"Nhưng ta cần hệ thống này hoàn thành càng sớm càng tốt. Sẽ mất bao lâu nếu ta cho mười phu tham gia thiết kế?"
Lập trình sư cau mày "Trường hợp này, ngài sẽ cần hai năm".
"Thế nếu ta đáp 100 phu vào thì sao?"
Lập Trình Sư nhún vai "Sẽ chẳng bao giờ xong" ông nói.

Theo bản dịch của anh HiepTH

Wednesday, June 2, 2010

Đạo code (Phần 2: Lập Trình Sư tiền bối)

Lập Trình Sư nói:
"Sau ba ngày không lập trình, cuộc sống trở nên vô nghĩa."

2.1

Bậc Lập Trình Sư tiền bối rất bí ẩn và uyên thâm. Chúng ta không thể hiểu suy nghĩ của họ. Thế nên ta chỉ mô tả đặc điểm của họ.
Họ cẩn thận như loài hồ ly trên mặt nước. Họ cảnh giác như vị tướng giữa trận tiền. Họ tốt bụng như chủ nhà tiếp bậc khách quý. Họ giản dị như gỗ đá. Họ bí ẩn như hồ đen trong hang tối.
Ai có thể biết bí mật trong con tim khối óc của họ.
Câu trả lời nằm trong Đạo.

2.2

Đại sư Turing một đêm nằm mơ thấy mình hóa máy. Khi thức dậy ông thốt lên:
"Không biết ta là Turing nằm mơ hóa máy hay ta là máy đang nằm mơ hóa Turing đây"

2.3

Một lập trình phu từ một đại công ty máy tính sau khi tham dự đại hội phần mềm trở về trình báo với chủ nhân: "Không hiểu lũ lập trình phu công ty khác là loại gì. Chúng cư xử rất thô lỗ và chẳng quan tâm gì đến vẻ bề ngoài. Đầu tóc chúng dài và bù xù, quần áo thì nhăn nhúm và cũ kỹ. Chúng nhảy xổ vào hội trường và gây huyên náo cả buổi."
Chủ nhân đáp "Đáng lẽ ta không nên đưa ngươi đến đó. Những người ngươi gặp sống ngoài thế giới thực. Họ coi cuộc sống là ngớ ngẩn, tất cả chẳng qua chỉ là một sự trùng hợp tình cờ. Họ đến và đi mà không hề biết đến giới hạn. Họ không quan tâm đến thứ gì khác ngoài chương trình của họ. Vậy thì họ còn bận tâm đến các vấn đề xã hội làm gì?"
"Họ đang sống trong Đạo"

2.4

Đồ đệ hỏi Lập Trình Sư: "Một lập trình phu không bao giờ thiết kế, thảo tài liệu, hay thử nghiệm chương trình. Vậy mà tất cả mọi người biết anh ta đều coi anh ta anh ta là thiên hạ đệ nhất phu. Tại sao vậy?"

Lập Trình Sư trả lời "Kẻ đó đã lĩnh hội được Đạo. Hắn đã thoát khỏi giới hạn của thiết kế, khi hệ thống trục trặc hắn không hề bối rối và tiếp nhận mọi thứ một cách bình thản. Hắn đã thoát khỏi giới hạn của thảo tài liệu, hắn không bao giờ e ngại khi có người khác review code của mình. Hắn đã thoát khỏi giới hạn của thử nghiệm. Mỗi đoạn code hắn viết đều đã sáng sủa, hoàn hảo và đẹp đẽ còn ý nghĩa của chúng đã hiển nhiên như cuộc sống.
Hắn đã đạt đến ngưỡng thần bí của Đạo.

Theo bản dịch của anh HiepTH

Đạo code (Phần 1: Cõi hư vô)

Lập trình sư nói:

"Khi ngươi học được cách lấy mã lỗi từ trong đoạn code bắt lỗi, ngươi có thể xuống núi"

1.1

Hình thành một cách thần bí, sinh ra từ hư vô. Đơn độc và bất động nhưng không ngừng vận động. Là nguồn gốc của mọi chương trình. Ta không biết tên nó là gì nên ta gọi nó là Đạo.

Đạo mà hay thì hệ điều hành sẽ hay. Hệ điều hành mà hay thì trình biên dịch sẽ hay. Trình biên dịch mà hay thì chương trình ắt sẽ hay. Chương trình hay thì bá tính an cư lạc nghiệp, vũ trụ thuận hòa.

Đạo bao trùm vạn vật, lan toả thiên hà. Tựa như cơn gió mát giữa mùa viêm nhiệt, như cơn mưa rào giữa tiết hanh khô.

1.2

Đạo sinh máy ngữ, máy ngữ sinh hợp ngữ, hợp ngữ sinh trình dịch, trình dịch sinh ngôn ngữ, ngôn ngữ biến hóa vô cùng (*).
Nay vạn ngữ đã hình thành. Một ngữ dù thấp kém đến đâu vẫn có mục đích của nó. Mỗi ngữ đều thể hiện tính âm dương của phần mềm. Mỗi ngữ đều có một vị trí trong Đạo.

Tuy nhiên nếu được lựa chọn, các ngươi không nên chọn ngữ COBOL để lập trình.

(*) Chú giải
Ngữ: Ngôn ngữ (lập trình)
Máy ngữ: ngôn ngữ máy - machine language
Hợp ngữ: assembly
Trình dịch: compiler

1.3

Khởi thủy từ Đạo. Đạo sinh thời gian và tài nguyên. Nên thời gian và tài nguyên chính là khía cạnh âm dương của Đạo vậy. (*)
Người lĩnh hội được Đạo luôn có đủ thời gian và tài nguyên để đạt được mục đích của mình. Kẻ không lĩnh hội được Đạo thì luôn thiếu. Không thể khác được.

(*) Chú giải
Thời gian và tài nguyên: Resource & Time, hai yếu tố tính size của phần mềm

1.4

Kẻ khôn ngoan khi nghe về Đạo thì làm theo. Kẻ bình thường khi nghe về Đạo thì nghiên cứu. Kẻ ngu dốt khi nghe về Đạo thì cười nhạo.
Nếu không có kẻ cười nhạo chắc hẳn đã không thành Đạo.


Âm cao tất khó nghe
Tiến lên dễ có đường
Nhân tài hay nở muộn
Code ngon lỗi như thường

Theo bản dịch của anh HiepTH