兩年來遵守的代碼風格

養成一個合格的編碼風格,有益於自己和閱讀你代碼的人理解:

**** 命名風格 ****

  1. 不允許任何類名,包名,方法名,變量名以下劃線_或者美元符號$開頭或者結尾
    反例:name, name, name, name, $name, name $

  2. 代碼中的命名不允許出現拼音,拼音與英語混合或者中文命名(專有名詞除外:alibaba,taobao,suzhou等)
    反例:dazhe, guanggao, 明天,getJihuoByDate()

  3. 類名使用大駝峯(UpperCamelCase)風格命名,但是PO/VO/DTO等例外
    舉例:XmlUtil, QrCode, TcpIpDeal
    反例:XMLUtil, HTTPUtil, TCPIPDeal, QRCode

  4. 方法名,參數,成員變量,局部變量使用小駝峯(lowerCamelCase)風格命名
    舉例:localValue, getUsernameById(Long id)

  5. 常量命名全部要大寫,單詞之間以單個下劃線_連接,儘量見名知意,不要嫌棄名字太長
    舉例:MAX_SERVER_COUNT, CACHE_EXPIRED_TIME
    反例:MAX_COUNT, EXPIRED_TIME

  6. 抽象類命名必須以AbstractBase開頭,異常類必須以Exception結尾,測試類必須以被測試類名爲開頭,以Test(s)結尾

  7. 數組命名:類型與方括號[]之間無空格相連定義
    舉例:String[] args
    反例:String args[]

  8. 需要序列化的類中boolean類型的變量名請勿使用is爲前綴,容易導致部分框架的序列化問題以及代碼getter/setter邏輯問題

  9. 包名統一使用小寫,點分隔符之間有且只有一個英語單詞;包名使用單數形式,但是類名有複數含義,則類名可以使用複數

  10. 子類和父類中間的成員變量名不能使用相同的變量名,避免造成誤讀,降低代碼可讀性

  11. 杜絕使用不規範的單詞縮寫,避免單詞歧義或者詞不達意

  12. 接口實現類,一定要用接口類名+Impl來定義

**** 常量 ****

  1. 關於常量,需要去判斷是否真正爲常量,並不僅僅是用static final定義來判斷
    static final int NUMBER = 5;
    static final ImmutableList NAMES = ImmutableList.of(“Ed”, “Ann”);
    static final Joiner COMMA_JOINER = Joiner.on(’,’); // because Joiner is immutable
    static final SomeMutableType[] EMPTY_ARRAY = {};
    enum SomeEnum { ENUM_CONSTANT }
    反例
    //缺少final
    static String nonFinal = “non-final”;
    //缺少static
    final String nonStatic = “non-static”;
    //set是可變的,即使是用static final修飾也不會是常量
    static final Set mutableCollection = new HashSet();
    //與上一個類似
    static final ImmutableSet mutableElements = ImmutableSet.of(mutable);
    static final Logger logger = Logger.getLogger(MyClass.getName());
    static final String[] nonEmptyArray = {“these”, “can”, “change”};
  2. 常量
    每個常量都是一個靜態final字段,但不是所有靜態final字段都是常量。在決定一個字段是否是一個常量時, 考慮它是否真的感覺像是一個常量。例如,如果任何一個該實例的觀測狀態是可變的,則它幾乎肯定不會是一個常量。 只是永遠不打算改變對象一般是不夠的,它要真的一直不變才能將它示爲常量
/**
1.不允許任何魔法值(即未預先定義的常量)直接出現在代碼中

2.long和Long初始賦值時,數值後應該使用大寫L,避免小寫l與數字1混淆,造成誤解

3.不要使用一個常量類維護所有常量,最好按照常量功能進行分類,分開維護
**/
// Constants
static final int NUMBER = 5;
static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(',');  // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }

// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set<String> mutableCollection = new HashSet<String>();
static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};

3. 代碼格式

  1. 大括號{}
    如果大括號內容爲空則簡潔地寫成{}即可,不需要換行;例外:
    左大括號不另起一行
    右大括號另起一行
    右大括號後有else/catch/finally等代碼塊時不換行,否則必須換行
// 多代碼塊的情況下,即使內容爲空,也應該換行
if (a == b) {
  // do something
} else if (a == c) {
} else {
}
try {
  // do something
} catch(Exception e) {
} finally {
}
  1. 小括號():左小括號與字符之間沒有空格,同理右小括號與字符之間也沒有空格
    反例:if ( a == b )

  2. if/else/for/while/do/swtich等保留字與括號之間必需有一個空格
    反例:for(int i = 0; i <= 10; i++){}, if(a == b){}

  3. 運算符:任何雙目、三目運算符的左右兩邊都必須有一個空格(賦值運算符,算數運算符,邏輯運算符,按位運算符)

舉例:
int a = 1;
int b += a;
int c = a - b;
int d = (c > 0) ? 1 : 0;

if ((d == a) && (a + c == 0)) {
  System.out.println(true);
} else {
}
  1. 縮進:縮進必須使用4個空格,嚴禁使用Tab控制符,更不要混用

  2. 單行註釋:雙斜線//與註釋內容之間有且僅有一個空格;單行註釋不要寫在代碼尾部

  3. 單行代碼字符不超過120(100)個,超出則必須在合適的位置換行,並且遵循下列規則:
    第二行相對第一行縮進8(4)個空格,第三方及以後與第二行保持垂直對齊
    運算符與下文一起換行(即運算符要在行首,不要放在行尾)
    方法調用的點符號.與下文一起換行
    方法調用中的多個參數需要換行時,在逗號後面換行(即不要把逗號放在行首)
    括號前不要換行

// 反例
StringBuilder sb = new StringBuilder();
sb.append('a').append('b')...append
  ("no line break here");
  1. 方法參數在定義與傳入時,多個參數逗號後必須有一個空格
 void excute(String arg1, String arg2, String arg3);
method(1, 2, 3);
  1. 單個方法的實現行數不要超過80行(註釋,方法簽名,左右大括號,方法內空行,回車及任何不可見字符除外)

4. OOP規約

  1. 避免使用類的實例訪問類的靜態變量或者今天方法(無謂增加編譯器的解析成本,使用類名訪問即可)

  2. 所有的重寫方法都必須添加@Override註解
    原因:
    1)避免某些字符相似導致的沒有重寫的錯誤;
    2)添加了註解之後,如果父類/接口簽名修改,實現類馬上報編譯錯誤

  3. 可變參數:相同的參數類型,相同的業務含義,才能使用Java的可變參數;請不要使用Object類型的可變參數
    // 舉例

public List<user> listUsers(Long...ids) {...}
  1. 外部正在使用的或者二方庫依賴的接口,都不允許修改方法簽名,以避免調用方產生影響;若方法已過時,則在方法上添加@Deprecated註解,並清晰地說明採用的新接口或者新的服務是什麼

  2. 不允許使用過時的類或者方法

  3. Object的equals方法易拋出空指針,使用時應該使用常量或者確定不爲空的對象調用equals(JDK7以上推薦使用:java.util.Objects#equals工具類來比較兩個對象)

舉例:"test".equals(obj)
  1. 所有相同類型的封裝類對象之間的值比較時,全部使用equals方法(最好使用工具類)
    封裝對象使用判斷時,比較的是對象地址
    延伸:對Integer對象在-128~127範圍內的賦值時,對象在IntegerCache.cache中產生,會服用已有對象,可以直接使用
    判斷;但是超出區間外的對象在堆中生成,不會複用

  2. 封裝類型和基本數據類型:
    所有POJO的成員變量必須使用封裝類型
    原因:POJO的屬性沒有初始值,是要提醒使用者使用時需要顯式賦值;POJO對應的數據庫數據表中的字段有可能爲空,使用基本數據類型自動拆箱,會有NPE(NullPointerException)的風險
    RPC方法的返回值和參數列表必須使用封裝類型
    原因:遠程服務調用如果返回基本數據類型,在某個服務失敗時會返回默認值(0/false)而不是null,會導致調用方邏輯判斷有誤,如果返回爲null,則調用方可以顯示錯誤或者拋出異常
    局部變量推薦使用基本數據類型

  3. POJO中的屬性不要賦初始值

  4. 所有實現了java.io.Serializable接口的類,都必須添加private static final long serialVersionUID屬性(請不要使用默認值-1也不要複製其他類的值,讓IDE自動生成,保證值唯一;請不要使用@SuppressWarnings忽略警告)

  5. 當序列化類中添加新屬性時,請不要修改serialVersionUID的值,防止反序列化失敗;如果要做不兼容升級,請修改serialVersionUID的值,防止反序列化混亂

  6. 構造方法中禁止添加業務邏輯代碼,初始化的邏輯代碼請放在init方法中

  7. POJO必須重寫toString方法,在Debug或者排查錯誤是,調用toString輸出POJO的屬性,便於排查問題

  8. 在POJO類中,禁止同時存在同一個屬性的getXxx()和isXxx()方法

  9. 當一個類中存在多個構造方法,或者存在多個同名方法時,請按照一定順序把所有相同的方法放置在一起

  10. 儘量不要在getter/setter方法中添加業務邏輯

5. 集合

  1. hashCode和equals
    只要重寫equals,則必須重寫hashCode
    使用自定義對象作爲Map的鍵時,必須重寫equals和hashCode

  2. Map的keySet(), values(), entrySet()方法返回的集合,不允許添加元素,否則會報UnsupportedOperationException

  3. Collections類emptyList(), singletonList()等方法返回的集合都是Immutable的,不可用添加或者刪除元素

  4. ArrayList的subList()方法返回的對象是SubList,不可強轉爲其他類型

  5. 在subList的場景中,對原集合元素個數的修改會導致對子集合的所有操作報ConcurrentModificationException

  6. 在把集合轉換成數組時,請使用 T[] toArray(T[] a)方法,不要使用無參的重載,實現如下:

  List<String> list = new ArrayList<>(2);
  list.add("1");
  list.add("2");
  String[] array = new String[list.size()];
  array = list.toArray(array);
  1. 在把數組轉成集合時,注意如下:
  String[] array = new String[] { "1", "2" };
  List<String> list = Arrays.asList(array);
  // List<String> list = Arrays.asList("1", "2");
  // 這行代碼會拋出UnsupportedOperationException
  list.add("3");
  // 這行代碼執行後,list.get(0)的值也會隨之修改
  str[0] = "0";
  //原因:asList()方法的返回值是java.util.Arrays.ArrayList,並沒有重寫AbstractList類實現的add方法;這個方法體現的是適配器模式,只作爲轉換接口使用,數據結構上仍是數組,
  //避免以上問題的寫法:new java.util.ArrayList<>(Arrays.asList("1", "2"));
  1. boolean addAll(Collection<? extends E> c)方法的調用,在調用Collection接口任何實現類的addAll方法時,傳入的參數都必須做NPE檢查

  2. 使用泛型通配符<? extends T>的集合,不能調用add方法,而<? super T>的集合不能調用get方法;根據PECS(Producer Extends, Consumer Super)原則:頻繁往外讀取數據適合使用<? extends T>,頻繁往裏插入內容適合使用<? super T>

  3. 把無泛型限制的集合賦值給有泛型的集合時,需要進行instanceof判斷,避免拋出ClassCastException

  4. 不要在foreach中添加或者刪除元素(不要在對集合遍歷時調用此集合的add/remove方法)

  List<String> list = new ArrayList<>(Arrays.asList("1", "2", "3"));
  // 錯誤寫法
  for (String str : list) {
      if (Objects.equals("1", str)) {
          list.add("4");
          continue;
      }
      if (Objects.equals("2", str)) {
          list.remove(str)
          break;
      }
  }
  // 正確寫法
  Iterator<String> iterator = list.iterator();
  while (iterator.hasNext()) {
      iterator.remove();
  }
  1. JDK7以上,使用泛型時,使用diamond語法(菱形語法):List list = new ArrayList<>();

  2. 對Map進行遍歷時使用entrySet而不是keySet,JDK8以上則使用Map.foreach
    注意:keySet方式遍歷Map其實是遍歷了兩次,一次是keySet生成Iterator,一次是從HashMap中取出key對應的value

6. 多線程、併發

  1. 創建單例對象是要保證線程安全:(推薦一下兩種方式,其他方式各有優缺點自行了解)

枚舉:最簡單,最優秀的創建單例的方式

public class SingletonEnum {

private SingletonEnum() {
}

private enum Singleton {
   INSTANCE;

   private final SingletonEnum instance;

   Singleton() {
       instance = new SingletonEnum();
   }

   public SingletonEnum getInstance() {
       return instance;
   }
}

public static SingletonEnum getInstance() {
   return Singleton.INSTANCE.getInstance();
}
}

內部類方式:既能保證線程安全,又不會大量JVM資源

public class SingletonClass {

private SingletonClass() {
}

// 外部類加載時並不需要立即加載內部類,內部類不被加載則不去初始化INSTANCE
// 調用的時候只創建一個 所以單例
private static class InstanceHolder {
   private final static SingletonClass instance = new SingletonClass();
}

public static SingletonClass getInstance() {
   return InstanceHolder.instance;
}
}
  1. 創建線程時,需要指定有意義的線程名稱,方便問題回溯

  2. 線程創建必須通過線程池提供,不允許顯式地自行創建線程
    線程池的好處就是減少線程創建和銷燬的開銷,極大地優化系統資源不足的問題

  3. 不允許使用Executors創建線程池,需要使用ThreadPoolExecutor的方式創建,這種方式可以更加明確線程池的允許規則

  4. JDK8使用Instant代替Date,使用LocalDateTime代替Calendar,使用DateTimeFormatter代替SimpleDateFormat時間操作更方便,線程安全

  5. 必須回收自定義的ThreadLocal變量,有可能導致內存泄漏OOM

  6. 高併發場景下,認真考量線程鎖,能不用鎖儘量不用,能不用帶鎖的數據結構就儘量不用,加鎖的代碼塊儘量小;避免在鎖中調用RPC服務

  7. 在對多個資源,數據表,對象加鎖時,保持一致的加鎖順序,避免死鎖

  8. 與資金相關的金融敏感信息使用悲觀鎖

7. 控制語句

  1. switch
    每個case語句要麼通過break/return來終止,要麼添加註釋說明要執行到哪個語句結束
    必須包含一個default語句,即使什麼邏輯都沒有

  2. 所有的代碼塊都必須包含大括號,即使代碼塊只有一行代碼(if/else/for/while/do)
    禁止單行編碼方式:if (true) doSomething();

  3. 禁止出現if-else if…else的代碼
    一定需要這種邏輯判斷的時候,代碼不能超過三層(即只允許出現一次else if),正確寫法(衛語句,策略模式,狀態模式實現):

// 衛語句
public void doSomething() {
  if (isBusy()) {
      // change time
      return;
  }

  if (isFree()) {
      // do something
      return;
  }

  // study
  return;
}
  1. 不要在判斷條件中執行復雜邏輯,除了一般常用的getXx()/isXx()/hasXx()/existXx()方法判斷外,判斷條件一般使用單個boolean值
  final boolean flag = (getXx() || hasXx()) && (a >= b || c != 0);
  if (flag) {
      // do something
  }
  1. 在高併發的場景下,不要使用==作爲程序中斷或者退出的判斷條件,因爲在併發處理錯誤的時候,會導致程序永遠都不會退出

  2. 循環體中的語句要考量性能。以下操作儘量提取到循環體外:
    定義對象、變量
    獲取數據庫連接
    不必要的try-catch

  3. 循環體/遞歸儘量不要超過兩次,禁止超過三層

  4. 避免不必要的取反邏輯

8. 註釋

  1. 類,類屬性,類方法的註釋都必須符合Javadoc規範,使用/註釋內容/格式,禁止使用單行註釋方式

  2. 所有的抽象方法(包括接口方法),都必須要用Javadoc註釋,清楚地說明參數,返回值,異常,以及該方法做什麼事情,實現什麼功能,對子類的實現有什麼要求,調用時需要注意什麼等

  3. 所有類必須添加創建者及創建時間

  4. 方法內的單行註釋,在被註釋代碼上方另起一行(禁止行尾註釋),使用//註釋;方法內的多行註釋,使用/* */註釋,注意代碼對齊

  5. 所有的枚舉類型字段必須要有註釋,說明每個數據的含義及用途

  6. 註釋可以使用中文,專有名詞和關鍵字注意保持與原文一致;註釋要語句通順,意思表達完整

  7. 修改代碼時,注意修改原註釋

  8. 註釋代碼時,請在被註釋掉的代碼上方,清楚地說明原因(使用///三斜線);如果代碼無用,請直接刪除

  9. 特殊註釋: TODO, FIXME
    待辦事項TODO: 在添加時,請寫清楚:標註人(執行人),標註時間,預計處理的時間,未實現的功能
    錯誤異常FIXME: 在添加時,請寫清楚:標註人(執行人),標註時間,預計處理的時間,使用fixme標記某代碼是錯誤的,不能工作,需要及時修改

異常日誌

  1. 關於RuntimeException
    類似NullPointException,IndexOutOfBoundsException等不應該使用catch來處理異常,而應該在代碼中通過判斷來規避這類異常
    // JDK8
    public final class Optional
    類似NumberFormatException可以使用catch捕獲並處理捕獲異常

  2. 捕獲的異常不允許不做任何處理就丟棄,如果不想處理則拋出給調用者
    最外層調用者一定要把異常處理成用戶可以理解的內容

  3. 在事務中,需要仔細判斷是否需要事務回滾,調用rollback
    不要在finally代碼塊中使用return語句
    捕獲的異常必須與拋出的異常類型完全匹配,或者捕獲拋出異常的父類型

  4. 大段的try-catch代碼需要對不同異常作出不同的應激反應,不允許對大量不同類型的異常統一捕獲Exception對象,不利於對不同問題作出對應的處理
    對應不同異常類型,但是處理方式一致的情況,使用multi-catch方式編碼

  5. 資源釋放
    必須在finally代碼塊中釋放掉所有的資源對象,流對象,關閉對象時的異常需要進行try-catch操作,禁止直接拋出

//JDK7及以上建議使用try-with-resource方式編碼
  try (InputStream is = new FileInputStream("text.txt")) {
      // do something
  } catch (IOException e) {
      // handle io exception
  }
  // 此處不需要顯式的通過finally塊關閉流,流會在代碼執行完畢後自動釋放
  1. 空值null
    允許方法返回null,但是最好儘量避免;在返回null的方法要註釋充分說明,什麼情況下才會返回null,調用方需要添加NPE判斷
    自動拆箱時,注意空指針
    數據庫查詢到的數據屬性可能是null
    集合即使isNotEmpty,取出的數據依然可能是null
    遠程調用返回對象時,一定要做NPE檢查
    級聯調用時注意空指針:obj.getA().getB().getC(),建議寫法:
  Optional.ofNullable(obj)
      .map(Obj::getA)
      .map(A::getB)
      .map(B::getC)
      .get()
  1. 自定義異常
    不要在代碼中顯式的拋出new RuntimeException(),更不允許拋出Exception或者Throwable
    根據自己的業務定義自定義異常(ServerException, UsernameExistException)
    定義ErrorCode

  2. 所有對外的接口,都必須有統一的數據結構和返回規則;統一的錯誤編碼可以更好幫助調用者定位問題或者處理邏輯

  3. 重複代碼
    不允許大段複製代碼(Don’t repeat yourself)即DRY原則
    把需要重複使用的代碼提取成共性代碼,公共方法等

  4. 日誌
    輸出日誌建議使用SLF4J,或者框架推薦日誌實現方式
    日誌文件至少保存15天以上
    輸出日誌時不要使用字符串拼接方式,建議使用日誌佔位符
    // 佔位符{}
    logger.info(“id-{}, message-{}”, id, message);
    對於debug/info/trace級別的日誌,必須使用條件輸出或者佔位符的方式輸出
    // 條件輸出
    if (logger.isDubugEnable()) {
    logger.debug(“id-” + id + “, message-” + message);
    }
    禁止在生產環境中使用System.out, System.err輸出日誌;禁止使用e.printStackTrace()輸出異常堆棧
    開發時可以用來打樁調試,提交代碼時務必刪除
    輸出異常信息時,需要輸出異常信息和堆棧;如果不做處理,請把異常往上一層拋出
    logger.error("異常信息toString: " + e.getMessage, e);
    生產環境禁止輸出debug級別的日誌,info級別的日誌請有選擇的輸出
    避免大量的無效日誌,大量的無效日誌及影響系統的運行,又不能快速的定位問題;輸出日誌時請認真思考這些日誌能用來做什麼,這些日誌能幫助排查問題嗎
    剛上線的項目允許通過warn級別輸出業務行爲信息,但是要注意輸出量,避免把服務器磁盤搞爆炸,並及時刪除日誌文件
    適當的使用warn級別日誌,記錄用戶輸入的錯誤參數情況,可以在用戶投訴時提供幫助
    推薦使用英文輸出日誌信息,當用英文表達不清楚時,可以使用中文(謹慎使用);對應國外服務器或者國際服務器或者中外同服的情況下,全部使用英文輸出日誌,避免字符集的問題

  5. 安全規約
    任何屬於用戶個人的頁面或者功能點都必須添加權限控制校驗
    用戶的敏感信息禁止直接展示,必須對展示的信息進行脫敏處理(部分/全部數據替換成***)
    注意:對外接口、服務返回的數據裏不要包含敏感數據
    密碼、手機號、身份證號、真實姓名等必要的數據

  6. SQL:用戶輸出的SQL參數必須進行嚴格的參數綁定和驗證,防止SQL注入;禁止使用字符串拼接SQL訪問數據庫

  7. 所有用戶輸入的數據都必須進行參數綁定和有效性驗證

  8. 在使用平臺資源(如:短信,郵件,電話,支付等)時,必須正確實現防重放機制(如:數量限制,疲勞值限制,驗證碼校驗),避免被濫刷或者資產損失

MYSQL

  1. 建表
    表達是否概念的字段一律使用is_xxx,數據類型爲unsigned tinyint(1表示是,0表示否)
    is_deleted:是否刪除
    表明或者字段名必須使用小寫字母或者數字,單詞之間用下劃線連接,禁止以數字打頭,禁止兩個下劃線之間只包含數字
    MYSQL在Windows下不區分大小寫,但是在Linux上默認區分大小寫,所有禁止在數據庫中使用大寫字母,避免不必要的麻煩
    表名不允許使用複數名稱命名
    禁止使用數據庫保留字(附:MYSQL8.0保留字庫)
    經常被使用的保留字:name(s), key(s), desc等,需要特別注意
    小數類型應該爲:decimal,禁止使用float/double
    如果存儲的字符串長度基本相等,應使用定長的char類型代替varchar。例如:MD5加密的密碼,以一定規則生成的KEY,UUID等
    varchar是可變長度字符串類型,不預先分配存儲空間,最大長度不要超過5000。如果存儲長度超過5000,需要使用text類型,並且建議建立關聯表以主鍵關聯,避免影響其他字段的索引效率
    允許數據表數據冗餘,提高查詢效率,但是必須考慮數據的一致性
    此冗餘字段不能頻繁修改
    此字段不能是超長的varchar或者text

  2. 單表數據超過500W行或者單表容量超過2GB時才推薦使用分表分庫

  3. 對字段設置合適的存儲長度,可以節約數據表空間和索引存儲,更重要的是可以提升檢索速度

索引

  1. 業務上具有唯一特效的字段必須建立唯一索引
    雖然唯一索引會影響插入速度,這個速度損耗可以忽略,但是會明顯的提升查詢速度
    根據墨菲定律,即使在應用層做了嚴格的參數驗證,只要沒有唯一索引,就一定會有髒數據產生

  2. 絕對禁止三個表以上的join操作
    需要join的字段,數據類型一定要一致
    當多表關聯查詢時,保證關聯字段要有索引
    即使雙表的join操作,也要注意索引和SQL性能
    在varchar字段上建立索引時,需要指定建立索引的長度
    模糊搜素時,禁止使用左模糊或者全模糊,如果需要請搜索索引文件,否則會導致索引失效

SQL

  1. 不要使用count(列名)或者count(常量)代替count()
    count(
    )是SQL92的標準統計行數的語法(跟數據庫無關,與NULL值也無關),並且MYSQL對其進行了深度優化
    count(列名)的方式會忽略掉此列爲NULL的數據行

  2. 使用SUM(列名)時,需要注意NPE問題
    使用IS NULL來判斷NULL。因爲NULL與其他任意值得比較結果均爲NULL,而不是TRUE/FALSE
    禁止在數據庫中使用外鍵或者級聯,一起外鍵的概念必須在應用層解決
    禁止使用存儲過程
    難以調試,難以擴展,沒有移植性

  3. 數據刪除或者修改時,必須先做查詢操作,確認無誤後,在查詢到的數據基礎上再做更新或者刪除

  4. 規避在SQL中使用in操作,如果無法避免,則要仔細評估in後面的集合元素數量,控制在1000個以內

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章