自己的前言說明: 本文原作者:Radoslaw Sadowski,原文鏈接為:C# BAD PRACTICES: Learn how to make a good code by bad example。 本系列還有其他文章,後續將慢慢翻譯。 引言: 我的名字叫Radoslaw Sadowski,我 ...
自己的前言說明:
本文原作者:Radoslaw Sadowski,原文鏈接為:C# BAD PRACTICES: Learn how to make a good code by bad example。
本系列還有其他文章,後續將慢慢翻譯。
引言:
我的名字叫Radoslaw Sadowski,我現在是一個微軟技術開發人員。我從開始工作時就一直接觸的微軟技術.
在工作一年後,我看到的質量很差的代碼的數量基本上都可以寫成一整本書了。
這些經歷讓我變成了一個想要清潔代碼的強迫症患者。
寫這篇文章的目的是為了通過展示質量很差的類的例子來說明如何書寫出乾凈的、可延伸的和可維護的代碼。我會通過好的書寫方式和設計模式來解釋壞的代碼帶來的問題,以及替換他的好的解決方法。
第一部分是針對那些擁有C#基礎知識的開發人員——我會展示一些常見的錯誤,然後再展示一些讓代碼變得可讀性的方法與技巧。高級部分主要針對那些至少擁有設計模式概念的開發人員——我將會展示完全乾凈的、單元可測試的代碼。
為了能夠理解這篇文章你需要至少掌握以下兩個部分的基本知識:
- C#語言
- 依賴註入、工廠設計模式、策略設計模式
本文中所涉及的例子都將會是現實中實實在在的具體的特性,而不是用裝飾模式來做披薩或者用策略模式來做計算器這樣的示例。
(ps解釋:看過設計模式相關的書籍的人應該會知道很多這方面的書籍都是用這種例子,只是為了幫助讀者理解設計模式)
因為我發現這種類型的產品不好用來解釋,相反這些理論性的例子卻是非常適合用來在本文中做解釋的。
我們常常會聽到說不要用這個,要用那個,但是卻不知道這種替換的理由。今天我將會努力解釋和證明那些好的書寫習慣以及設計模式是真的是在拯救我們的開發生活!
提示:
- 在本文中我不會花時間來講解C#的特性和涉及模式之類(我也解釋不完),網上有很多關於這方面的好的理論的例子。我將集中講述如何在我們日常工作中使用這些東西。
- 例子是一種比較容易的突出我們要說明的問題的方法,但是僅限於描述的問題——因為我發現當我在學習哪些包含著主要代碼的例子時,我發現在理解文章的總體思想方面會有困難。
- 我不是說我文中說的方法是惟一的解決方式,我只是能保證這些方法將會是讓你的代碼變得更高質量的途徑。
- 我並不關心下麵這些代碼的什麼錯誤處理,日誌記錄等等。我要表述的只是用來解決日常編碼一些問題的方法。
那就開始吧….
那些糟糕透了的類...
下麵的例子是我們現實中的類:
1 public class Class1 2 { 3 public decimal Calculate(decimal amount, int type, int years) 4 { 5 decimal result = 0; 6 decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100; 7 if (type == 1) 8 { 9 result = amount; 10 } 11 else if (type == 2) 12 { 13 result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount)); 14 } 15 else if (type == 3) 16 { 17 result = (0.7m * amount) - disc * (0.7m * amount); 18 } 19 else if (type == 4) 20 { 21 result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount)); 22 } 23 return result; 24 } 25 }
上面這個例子真的是一種非常差的書寫方式。你能知道這個類是用來幹嘛的麽?這個東西是用來做一些奇怪的運算的麽?我們文章就從他開始入手來講解吧…
現在我來告訴你,剛剛那個類是用來當顧客在網上買東西的時候為他們計算對應折扣的折扣計算和管理的類。
-難以置信吧!
-可是這是真的!
這種寫法真的是將難以閱讀、難以維護和難以擴展這三種集合在一起了,而且擁有著太差的書寫習慣和錯誤的模式。
除此之外還有其他什麼問題麽?
1.命名方式-從源代碼中我們可以連蒙帶猜估計出來這個計算方法和輸出結果是什麼。而且我們想要從這個類中提取計算演算法將會是一件非常困難的事情。
這樣帶來的危害是:
最嚴重的問題是:浪費時間,
如果我們需要滿足客戶的商業咨詢,要像他們展示演算法細節,或者我們需要修改這段代碼,這將花費我們很長的時間去理解我們的計算方法的邏輯。即使我們不記錄他或重構代碼,下次我們/其他開發人員再看這段代碼的時候,還是需要花費同等的時間來研究這些代碼是幹嘛的。而且在修改的同時還容易出錯,導致原來的計算全部出錯。
2.魔法數字
在這個例子中type是變數,你能猜到它代表著客戶賬戶的等級麽?If-else if語句是用來實現如何選擇計算出產品價格折扣的方法。
現在我們不知道什麼樣的賬戶是1,2,3或4。現在想象一下,當你不得不為了那些有價值的VIP客戶改變他們的折扣計算方式的時候,你試著從那些代碼中找出修改的方法---這個過程可能會花費你很長的時間不說,還很有可能犯錯以至於修改那些基礎的一般的客戶的賬戶,畢竟像2或者3這些詞語毫無描述性的。但是在我們犯錯以後,那些一般的客戶卻很高興,因為他們得到了VIP客戶的折扣。:)
3.沒有明顯的bug
因為我們的代碼質量很差,而且可讀性非常差,所以我們可能輕易就忽略掉很多非常重要的事情。想象一下,現在突然在系統中增加一種新的客戶類型-金卡用戶,而在我們的系統中任何一種新的賬戶類型最後獲得的價格將是0元。為什麼呢?因為在我們的if-else if語句中沒有任何狀態是滿足新的狀態的,所以只要是未處理過的賬戶類型,最後返回值都將變成0。一旦我們的老闆發現這件事,他將會大發雷霆-畢竟他已經免費賣給這樣用戶很多很多東西了!
4.沒有可讀性
我們必須承認上面這段代碼的可讀性是真的糟糕。
她讓我們花費了太多的時間去理解這段代碼,同時代碼隱藏錯誤的幾率太大了,而這就是沒有可讀性的最重要的定義。
5.魔法數字(再次)
你從代碼中能知道類似0.1,0.7,0.5這些數字的意思麽?好的,我承認我不知道。只有我們自己編寫這些代碼我們才知道這是什麼意思,別人是無法理解的。
你試試想想如果讓你修改下麵這句代碼,你會怎麼樣:
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
因為這個方法完全不可讀,所以你修改的過程中只能嘗試著把第一個0.5改成0.4而保持第二個0.5不懂。這可能會是一個bug,但是卻是最好的最合適的修改方式。因為這個0.5什麼都沒有告訴我們。
同樣的事也存在將years變數轉換到disc變數的轉換過程中:
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;
這是用來計算折扣率的,會通過賬戶在我們系統的時間的百分比來獲取。好的,那麼現在問題來了,如果時間剛剛好就是5呢?
6.簡潔-不要反覆做無用功
雖然第一眼看的時候不容易看出來,但是仔細研究一下就會發現:我們的代碼里有很多重覆的地方。例如:disc * (amount - (0.1m * amount));
而與之有同樣效果的還有(只是變了一個參數而已):disc * (amount - (0.5m * amount))
在這兩個算術中,唯一的區別就只是一個靜態參數,而我們完全可以用一個可變的參數來替代。
如果我們不試著在寫代碼的時候從一直ctri+c,ctrl+v中擺脫出來,那我們將遇到的問題就是我們只能修改代碼中的部分功能,因為我們不知道有多少地方需要修改。上面的邏輯是計算出在我們系統中每個客戶對應年限獲得的折扣,所以如果我們只是貿然修改兩到三處,很容易造成其他地方的前後不一致。
7.每個類有著太多的複雜的責任區域
我們寫的類至少背負了三個責任:
- 選擇計算的運演算法則
- 為每個不同狀態的賬戶計算折扣率
- 根據每個客人的年限計算出對應的折扣率
這個違背了單一責任原則。那麼這會帶來什麼危害呢?如果我們想要改變上訴3個特性中的兩個,那就意味著可能會碰觸到一些其他的我們並不想修改的特性。所以在修改的時候我們不得不重新測試所有的類,那麼這就造成了很重的時間的浪費。
那就開始重構吧…
在接下來的9個步驟中我將向你展示我們如何避免上訴問題來構建一個乾凈的易維護,同時又方便單元測試的看起來一目瞭然的代碼。
I:命名,命名,命名
恕我直言,這是代碼中最重要的一步。我們只是修改方法/參數/變數這些的名字,而現在我們可以直觀的瞭解到下麵這個類代表什麼意思。
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, int accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; 7 if (accountStatus == 1) 8 { 9 priceAfterDiscount = price; 10 } 11 else if (accountStatus == 2) 12 { 13 priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price))); 14 } 15 else if (accountStatus == 3) 16 { 17 priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price)); 18 } 19 else if (accountStatus == 4) 20 { 21 priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price))); 22 } 23 24 return priceAfterDiscount; 25 } 26 }
雖然如此,我們還是不理解1,2,3,4代表著什麼,那就繼續往下吧!
II:魔法數
在C#中避免出現不理解的魔法數的方法是通過枚舉來替代。我通過枚舉方法來替代在if-else if 語句中出現的代替賬戶狀態的魔法數。
1 public enum AccountStatus 2 { 3 NotRegistered = 1, 4 SimpleCustomer = 2, 5 ValuableCustomer = 3, 6 MostValuableCustomer = 4 7 }
現在在看我們重構了的類,我們可以很容易的說出那個計演算法則是用來根據不用狀態來計算折扣率的。將賬戶狀態弄混的幾率就大幅度減少了。
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; 7 8 if (accountStatus == AccountStatus.NotRegistered) 9 { 10 priceAfterDiscount = price; 11 } 12 else if (accountStatus == AccountStatus.SimpleCustomer) 13 { 14 priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price))); 15 } 16 else if (accountStatus == AccountStatus.ValuableCustomer) 17 { 18 priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price)); 19 } 20 else if (accountStatus == AccountStatus.MostValuableCustomer) 21 { 22 priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price))); 23 } 24 return priceAfterDiscount; 25 } 26 }
III:更多的可讀性
在這一步中我們將通過將if-else if 語句改為switch-case 語句,來增加文章的可讀性。
同時,我也將一個很長的計算方法拆分為兩句話來寫。現在我們將“ 通過賬戶狀態來計算折扣率”與“通過賬戶年限來計算折扣率”這兩者分開來計算。
例如:priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
我們將它重構為:priceAfterDiscount = (price - (0.5m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage *
priceAfterDiscount);
這就是修改後的代碼:
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; 7 switch (accountStatus) 8 { 9 case AccountStatus.NotRegistered: 10 priceAfterDiscount = price; 11 break; 12 case AccountStatus.SimpleCustomer: 13 priceAfterDiscount = (price - (0.1m * price)); 14 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 15 break; 16 case AccountStatus.ValuableCustomer: 17 priceAfterDiscount = (0.7m * price); 18 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 19 break; 20 case AccountStatus.MostValuableCustomer: 21 priceAfterDiscount = (price - (0.5m * price)); 22 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 23 break; 24 } 25 return priceAfterDiscount; 26 } 27 }
IV:沒有明顯的bug
我們終於找到我們隱藏的bug了!
因為我剛剛提到的我們的方法中對於不適合的賬戶狀態會在造成對於所有商品最後都返回0。雖然很不幸,但卻是真的。
那我們該如何修複這個問題呢?那就只有通過沒有錯誤提示了。
你是不是會想,這個會不會是開發的例外,應該不會被提交到錯誤提示中去?不,他會的!
當我們的方法通過獲取賬戶狀態作為參數的時候,我們並不想程式讓我們不可預知的方向發展,造成不可預計的失誤。
這種情況是絕對不允許出現的,所以我們必須通過拋出異常來防止這種情況。
下麵的代碼就是通過拋出異常後修改的以防止出現不滿足條件的情況-修改方式是將拋出異常防止 switch-case語句中的default 句中。
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; 7 switch (accountStatus) 8 { 9 case AccountStatus.NotRegistered: 10 priceAfterDiscount = price; 11 break; 12 case AccountStatus.SimpleCustomer: 13 priceAfterDiscount = (price - (0.1m * price)); 14 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 15 break; 16 case AccountStatus.ValuableCustomer: 17 priceAfterDiscount = (0.7m * price); 18 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 19 break; 20 case AccountStatus.MostValuableCustomer: 21 priceAfterDiscount = (price - (0.5m * price)); 22 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 23 break; 24 default: 25 throw new NotImplementedException(); 26 } 27 return priceAfterDiscount; 28 } 29 }
V:分析計算方法
在我們的例子中我們有兩個定義給客戶的折扣率的標準:
- 賬戶狀態;
- 賬戶在我們系統中存在的年限
對於年限的計算折扣率的方法,所有的計算方法都有點類似:
(discountForLoyaltyInPercentage * priceAfterDiscount)
當然,也還是存在例外的:0.7m * price
所以我們把這個改成這樣:price - (0.3m * price)
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; 7 switch (accountStatus) 8 { 9 case AccountStatus.NotRegistered: 10 priceAfterDiscount = price; 11 break; 12 case AccountStatus.SimpleCustomer: 13 priceAfterDiscount = (price - (0.1m * price)); 14 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 15 break; 16 case AccountStatus.ValuableCustomer: 17 priceAfterDiscount = (price - (0.3m * price)); 18 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 19 break; 20 case AccountStatus.MostValuableCustomer: 21 priceAfterDiscount = (price - (0.5m * price)); 22 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 23 break; 24 default: 25 throw new NotImplementedException(); 26 } 27 return priceAfterDiscount; 28 } 29 }
現在我們將整理所有通過賬戶狀態的計算方法改為同一種格式:price - ((static_discount_in_percentages/100) * price)
VI:通過其他方式再擺脫魔法數
接下來讓我們的目光放在通過賬戶狀態計算折扣率的計算方法中的靜態變數:(static_discount_in_percentages/100)
然後帶入下麵數字距離試試:0.1m,0.3m,0.5m
這些數字其實也是一種類型的魔法數-他們也沒有直接告訴我們他們代表著什麼。
我們也有同樣的情況,比如將“有賬戶的時間”折價為“忠誠折扣”。
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
數字5讓我們的代碼變得神秘了起來。
我們必須做些什麼讓這個變得更具表現性。
我會用另外一種方法來避免魔法數的表述的出現-也就是C#中的常量(關鍵詞是const),我強烈建議在我們的應用程式中專門定義一個靜態類來存儲這些常量。
在我們的例子中,我是創建了下麵的類:
1 public static class Constants 2 { 3 public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5; 4 public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m; 5 public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m; 6 public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m; 7 }
經過一定的修改,我們的DiscountManager類就變成了這樣了:
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; 7 switch (accountStatus) 8 { 9 case AccountStatus.NotRegistered: 10 priceAfterDiscount = price; 11 break; 12 case AccountStatus.SimpleCustomer: 13 priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price)); 14 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 15 break; 16 case AccountStatus.ValuableCustomer: 17 priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price)); 18 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 19 break; 20 case AccountStatus.MostValuableCustomer: 21 priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price)); 22 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); 23 break; 24 default: 25 throw new NotImplementedException(); 26 } 27 return priceAfterDiscount; 28 } 29 }
我希望你也認同我這個方法會更加使代碼自身變得更具有說明性:)
VII:不要再重覆啦!
我們可以通過分拆演算法的方式來移動我們的計算方法,而不是僅僅簡單的複製代碼。
我們會通過擴展方法。
首先我們會創建兩個擴展方法。
1 public static class PriceExtensions 2 { 3 public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize) 4 { 5 return price - (discountSize * price); 6 } 7 8 public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYears) 9 { 10 decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; 11 return price - (discountForLoyaltyInPercentage * price); 12 } 13 }
正如方法的名字一般,我不再需要單獨解釋一次他們的功能是什麼。現在就開始在我們的例子中使用這些代碼吧:
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) 4 { 5 decimal priceAfterDiscount = 0; 6 switch (accountStatus) 7 { 8 case AccountStatus.NotRegistered: 9 priceAfterDiscount = price; 10 break; 11 case AccountStatus.SimpleCustomer: 12 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS) 13 .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); 14 break; 15 case AccountStatus.ValuableCustomer: 16 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS) 17 .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); 18 break; 19 case AccountStatus.MostValuableCustomer: 20 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS) 21 .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears); 22 break; 23 default: 24 throw new NotImplementedException(); 25 } 26 return priceAfterDiscount; 27 } 28 }
擴展方法讓代碼看起來更加友善了,但是這個代碼還是靜態的類,所以會讓你單元測試的時候遇到困難,甚至不可能。那麼出於擺脫這個問題的打算我們在最後一步來解決這個問題。我將展示這些是如何簡化我們的工作生活的。但是對於我個人而言,我喜歡,但是並不算是熱衷粉。
不管怎樣,你現在同意我們的代碼看起來友善多了這一點麽?
那我們就繼續下去吧!
VIII:移除那些多餘的代碼
在寫代碼的時候原則上是我們的代碼越是精簡越好。精簡的代碼的意味著,越少的錯誤的可能性,在閱讀理解代碼邏輯的時候花費的時間越少。
所以現在開始精簡我們的代碼吧。
我們可以輕易發現我們三種客戶賬戶下有著相同的方法:
.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
我們可不可以只寫一次呢?我們之前將未註冊的用戶放在了拋出異常中,因為我們的折扣率只會計算註冊用戶的年限,並沒有給未註冊用戶留有功能設定。所以,我們應該給未註冊用戶設定的時間為多少呢? -0年
那麼對應的折扣率也將變成0了,這樣我們就可以安全的將折扣率交付給未註冊用戶使用了,那就開始吧!
1 public class DiscountManager 2 { 3 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int