幾乎我們所有的代碼都是樣板:我們不斷重復模式和代碼段,卻很少改動每個類和項目。那么,到底該如何更有趣、更有效的進行呢?
譯者 | 彎月
責編 | Elle
以下為譯文:
雖然很可悲,但我不得不承認:我們編寫代碼的能力越強,獲得的樂趣就越少。我們都知道SOLID原則、不可變性、抽象、組成和可維護的代碼。但是,當我們在實際的編程(Java或C#編程)中嘗試使用這些原則時卻總是覺得有問題。幾乎我們所有的代碼都是樣板:我們不斷重復模式和代碼段,卻很少改動每個類和項目。編程的工作變得如此單調,我們需要輸入大量代碼才能產生少量的功能。
我發現,當人們抱怨Java的使用(這是如今的流行趨勢)時,通常是因為他們開發了一種編寫可維護代碼的方法,但是Java對此一無所知,而且Java還沒有足夠的表達能力來簡潔地描述這種方法。
在本文中,為了演示這一點,我們將利用Java構建一個玩具應用程序,然后再與一種更有效、更有趣的表達方法進行比較。我們將在文章的末尾比較這兩種方法,以證明標題中的數字很準確。
我們將創建一個查詢數據庫的程序,通過ID獲取特定的用戶,并將該用戶名中的字母數輸出到控制臺。雖然這個應用程序并沒有實用性,但足以表明我的意思。
我們將在文中展示代碼示例,但我想強調的是,你不必仔細分析每一行代碼,只需大致瀏覽類的定義并看清代碼中的模式。
Java的方法
下面讓我們開始。首先我們編寫一個Java類:
publicclassUsernameLetterCountPublisher{
}
這一段代碼全是模板。我們的應用尚無具體的內容,只有一個名字,但是創建類是一項我們需要反復執行的任務,卻無需太多改動,因此我認為這種代碼就是樣板。
UsernameLetterCountPublisher有一個從在數據庫中查找用戶的依賴項。我們希望盡可能地降低UsernameLetterCountPublisher類與其依賴項的耦合。這主要是因為如此一來,我們就可以在沒有實際數據庫的情況下對其進行測試,而且還可以減少將來更改這個類的可能性。我們定義一個接口,并將該接口的實例注入UsernameLetterCountPublisher的構造函數中,如下所示:
publicclassUsernameLetterCountPublisher{
privatefinalUserProvider userProvider;
publicUsernameLetterCountPublisher( finalUserProvider userProvider) {
this.userProvider = userProvider;
}
}
通常,我們都會通過構造函數注入單元的依賴項,所以這段代碼也是模板。我們定義的UserProvider如下所示:
publicinterfaceUserProvider{
User execute( finalString id) ;
}
還有User數據類型:
publicclassUser{
publicfinalString username;
publicUser( finalString username) {
this.username = username;
}
@Override
publicboolean equals(Object o) {
if( this== o) returntrue;
if(o == null|| getClass != o.getClass) returnfalse;
User user = (User) o;
returnObjects.equals(username, user.username);
}
@Override
publicint hashCode {
returnObjects.hash(username);
}
@Override
publicString toString {
return"User{"+ " username="+ username + "}";
}
}
你可能會說UserProvider接口中包含了應用程序的特定邏輯,但我認為通過ID查找實體并沒有特定于某個應用程序。我認為這里唯一的非樣板代碼(除命名之外)是User對象擁有的username字段。User類的定義,例如為了保證不可變性它的構造中包含了public final,以及toString、equals和hashCode函數的定義對于所有數據結構都是相同的。
另一種方法:UnitilyLang
接下來,讓我們定義一種自己的編程語言,取名為UnitilyLang,該語言的設計旨在為我們提供簡潔的編程模式。Java代碼與UnitilyLang的實現之間存在一對一的映射,但是后者要簡潔得多。這凸顯了Java方法中的樣板代碼。我們可以使用UnitilyLang,僅用30個字符替代Java代碼的最后兩部分(596個字符):
data User
username: String
上述代碼中的data表明這個類僅帶有public final字段,而且我們可以針對這些字段應用equals、hashCode和toString函數,并通過該類的構造函數進行初始化。User是這個類的名稱,username是其唯一的字符串類型的字段。我們將在本文后面介紹為什么我們不需要定義接口。
下面我們需要創建UserProvider接口的實現,這個接口將從DynamoDB(一個AWS的No SQL數據庫,你不需要了解太多)中讀取數據。
publicclassDynamoDbUserProvider{
privatefinalConfigProvider configProvider;
privatefinalUserTableProvider userTableProvider;
privatefinalDynamoItemReader dynamoReader;
privatefinalItemToUserConverter itemToUserConverter;
publicDynamoDbUserProvider(
finalConfigProvider configProvider,
finalUserTableProvider userTableProvider,
finalDynamoItemReader dynamoReader,
finalItemToUserConverter itemToUserConverter) {
this.configProvider = configProvider;
this.userTableProvider = userTableProvider;
this.dynamoReader = dynamoReader;
this.itemToUserConverter = itemToUserConverter;
}
publicUser execute( finalString id) {
finalConfig config = configProvider.execute;
finalDynamoTable dynamoTable = userTableProvider.execute(config);
finalItem item = dynamoReader.execute(dynamoTable, id);
finalUser user = itemToUserConverter.execute(item);
returnuser;
}
}
我們稱DynamoDbUserProvider單元為工作流單元。工作流單元不需要執行任何操作,只是為了組成其依賴關系的執行。創建抽象時我們需要這樣的工作流單元,例如我們可以有一個名為DynamoDbUserProvider的單元,其功能很明顯,所以我們無需在意其內部工作原理。
DynamoDbUserProvider單元擁有四個依賴項。為了避免本文涉及過多邏輯,我不打算展示dynamoReader的代碼。我也沒有展示ConfigProvider依賴的代碼。它的實現通常為:讀取環境變量或磁盤上的文件,并提供應用程序級的Config,這是另一個數據類。在我們的示例中,Config的代碼具體如下:
publicclassConfig{
publicfinalDynamoTable userTable;
publicfinalString userId;
publicConfig( finalDynamoTable userTable, finalString userId) {
this.userTable = userTable;
this.userId = userId;
}
@Override
publicboolean equals(Object o) {
if( this== o) returntrue;
if(o == null|| getClass != o.getClass) returnfalse;
Config config = (Config) o;
returnObjects.equals(userTable, config.userTable) && Objects.equals(userId, config.userId);
}
@Override
publicint hashCode {
returnObjects.hash(userTable, userId);
}
@Override
publicString toString {
return"Config{"+ " userTable="+ userTable + " userId="+ userId + "}";
}
}
它還有一個DynamoTable,我們在查找某個表時需要用到這個字段,這是另一個數據類:
publicclassDynamoTable{
publicfinalString region;
publicfinalString tableName;
publicfinalString idFieldName;
publicDynamoTable( finalString region, finalString tableName, finalString idFieldName) {
this.region = region;
this.tableName = tableName;
this.idFieldName = idFieldName;
}
@Override
publicboolean equals(Object o) {
if( this== o) returntrue;
if(o == null|| getClass != o.getClass) returnfalse;
DynamoTable that = (DynamoTable) o;
returnObjects.equals(region, that.region)
&& Objects.equals(tableName, that.tableName)
&& Objects.equals(idFieldName, that.idFieldName);
}
@Override
publicint hashCode {
returnObjects.hash(region, tableName, idFieldName);
}
@Override
publicString toString {
return"DynamoTable{"
+ "region='"
+ region
+ '''
+ ", tableName='"
+ tableName
+ '''
+ ", idFieldName='"
+ idFieldName
+ '''
+ '}';
}
}
上述DynamoDbUserStore也需要依賴一個單元來提供我們所需的某一段config:
publicclassUserTableProvider{
publicDynamoTable execute( finalConfig config) {
returnconfig.userTable;
}
}
還有一個ItemToUserConverter是為了將AWS DynamoDb SDK的結果(Item)轉換成我們的數據類User。
publicclassItemToUserConverter{
publicUser execute( finalItem item) {
String username = item.getString( "username");
User user = newUser(username);
returnuser;
}
}
這又是一段樣本代碼。我們構建的每一個應用都會重復使用ConfigProvider的實現,實際的Config會有所不同,但是創建Config的邏輯不會變,所以我認為這也是樣板。DynamoItemReader依賴項也是如此。最后,我們所有的服務類都通過構造函數注入了它們的依賴關系,并作為private final字段保存到了我們所有的項目中。
DynamoDbUserProvider中唯一涉及應用程序特定邏輯的就是命名,及其依賴項組合在一起的順序,目的是為了通過id獲取User。
如果使用UnitilyLang的話,我們可以用(248個字符)替換前面的5段代碼(隱藏在ConfigProvider和DynamoItemReader類中的3006個字符在UnitilyLang中都沒有必要)。
data DynamoTable
region: String
tableName: String
idFieldName: String
data Config
table: DynamoTable
userId: String
pure ItemToUserConverter: Item -> User
item -> newUser(item.getString( "name"))
workflow DynamoDbUserStore
= 4. ( 3( 21))
我們可以仿照User對象的定義,將Config和DynamoTable聲明為數據類。
要定義ItemToUserConverter,我們只能使用目標編程語言(在本例中為Java)。UnitilyLang的設計目標是抽象、不可變性和組合。除了編寫邏輯之外,它都與語言無關。為了定義純單位,我們需要編寫一些代碼。
工作流單位
工作流的聲明為通過構造函數注入的每個依賴項定義了一個帶有private final字段的類。至少也定義了DynamoDbUserProvider的功能。如果本段的后續內容不是特別清晰,那么也請不要擔心,因為我的寫作能力不足,我們稍后會以更清晰的方式進行說明。下面,我們開始吧……每個數字n對應于第n個依賴項。聲明4 . (3 (2 1))定義了這些依賴關系之間的組合方式。我們將第二個依賴項應用于第一個依賴項的結果。然后,再部分地應用第3個依賴關系,并用第4個依賴關系組成結果函數。這種聲明函數的方式稱為零點風格(zero point style)。
UnitilyLang可以用更少的代碼表達相同含義,原因僅僅是因為Java編程語言和庫并沒有按照最自然的方式表達我們遵循的簡潔代碼模式。UnitilyLang可能更簡潔,但仍然沒有按照自然的方式表達DynamoDbUserStore單元的工作,即構成其依賴項的方式。上一段的自然語言描述顯然沒有解釋清楚這一問題。那么,什么才是更自然的方式呢?他們說一圖勝千言,所以讓我們來畫一張DynamoDbUserProvider。
上圖就相當于4 . (3 (2 1)),只是更容易理解,我相信你看得懂,所以就不多做解釋了。
關于UnitilyLang(和上圖)工作流單位的有趣之處在于它們是通用的。它們不會因依賴關系的類型而有所變化。只要在實例化時每個箭頭末尾處的類型相同,就可以通過編譯。這實際上限制了函數能做的事情,并更容易推理。它們只能使用依賴項執行的結果。這也意味著我們不需要聲明接口,任何類型正確的依賴項都可以使用。
Java與UnitilyLang之間、UnitilyLang與上圖之間存在一對一的映射。上圖遠比你想象得更好。與代碼相比,人類在圖片的理解和推理方面擁有無限的優勢。重構也要容易得多,如果你想更改數據流的方式,那么只需擦掉一個箭頭,并重新畫出指向其他方向的箭頭即可!
現在,我們已經編寫了足夠的代碼,本文開頭的UsernameLetterCountPublisher已經編寫完成。我們需要為它創建execute方法來查詢數據庫,統計字母數并在屏幕上顯示一條消息。
publicclassUsernameLetterCountPublisher{
privatefinalConfigProvider configProvider;
privatefinalUserIdProvider userIdProvider;
privatefinalUserProvider userProvider;
privatefinalUserMessageCreator userMessageCreator;
privatefinalPublisher publisher;
publicUsernameLetterCountPublisher(
finalConfigProvider configProvider,
finalUserIdProvider userIdProvider,
finalUserProvider userProvider,
finalUserMessageCreator userMessageCreator,
finalPublisher publisher) {
this.configProvider = configProvider;
this.userIdProvider = userIdProvider;
this.userProvider = userProvider;
this.userMessageCreator = userMessageCreator;
this.publisher = publisher;
}
publicvoid execute {
finalConfig config = configProvider.execute;
finalString id = userIdProvider.execute(config);
finalUser user = userProvider.execute(id);
finalString message = userMessageCreator.execute(user);
publisher.execute(message);
}
}
我們快速介紹一下它的依賴項。我們重用ConfigProvider并注入了一個UserIdProvider:
publicclassUserIdProvider{
String getUserId(ApplicationConfig applicationConfig){
returnapplicationConfig.userId;
}
}
目的是為了獲得某段config,就像我們在加載userTableName時的做法。我們還注入了一個UserMessageCreator類:
publicclassUserMessageCreator{
publicString execute( finalUser user) {
String message =
String.format( "%s has %d letters in his/her name", user.username, user.username.length);
returnmessage;
}
}
這個單元很純粹,而且與其父級的命名相關聯,因此我并不覺得需要通過更抽象的公共接口來引用它。此外,還有一個Publisher依賴項。這個依賴項并不是很純粹(會影響到屏幕上的輸出),我們想要使用多種實現方式,例如模擬測試,因此我們定義了一個需要注入的接口。
publicinterfacePublisher{
voidexecute( finalString x) ;
}
而且還創建了真正的應用實現:
publicclassConsolePrinter{
publicvoidexecute( final String x) {
System. out.println(x);
}
}
我們認為這個通用名稱最合適當前的各個類,如果我們選擇發布到消息隊列(而不是控制臺),那么Publisher仍然說得通。接下來,我們可以注入MessageQueuePublisher(而不是ConsolePrinter),同時無需對UsernameLetterCountPublisher類進行任何修改。敏銳的你可能已經注意到,我們沒有使用Implements Publisher語句標記ConsolePrinter類,也沒有在UserProvider接口的DynamoDbUserProvider實現中加入該操作。我們將在本文結尾處說明為什么不實例化單元的原因。
同樣,大多數UsernameLetterCountPublisher代碼都是樣板。你可以通過比較DynamoDbUserProvider來確認這種重復的模式。我們需要定義如何構成其依賴關系(就像我們處理DynamoDbUserProvider的那樣),而且UserMessageCreator和ConsolePrinter中有一些特定于應用程序的邏輯,但這就是所有我們必須定義的內容。
因此,上述5段代碼(1545個字符)可以被UnitilyLang的274個字符代替:
pure UserMessageCreator: User -> String
user -> String.format( "%s has %d letters in his/her name", user.username, user.username.length)
sideeffect ConsolePrinter: String ->
message -> System.out.println(message)
workflow UsernameLetterCountPublisher
= 5( 4( 3( 21)))
之前我們已經見過與UserMessageCreator和UsernameLetterCountPublisher類似的聲明。副作用聲明類似于純單元聲明,但我們可以得知這會導致副作用。
我們可以通過下圖可視化UsernameLetterCountPublisher。
單元測試與集成測試
到此為止,我們編寫好了完成任務所需的所有代碼。現在,我們需要編寫一些測試和一個程序來運行代碼。猜猜下一步是什么?沒錯,更多樣板代碼。
我們將從純單元開始,下面是UserMessageCreator的測試代碼:
publicclassUserMessageCreatorTest{
privatestaticfinalUser user;
privatestaticfinalString message;
privateUserMessageCreator userMessageCreator;
static{
finalString username = "aUsersName";
user = newUser(username);
message = "aUsersName has 10 letters in his/her name";
}
@BeforeEach
publicvoidsetupTestFixture{
userMessageCreator = newUserMessageCreator;
}
@Test
publicvoidtest1{
assertEquals(
message,
userMessageCreator.execute(user),
"should create a message containing the number of characters in the username");
}
}
比較一下我們的另一個純單元ItemToUserConverter的測試:
publicclassItemToUserConverter{
privatestaticfinalItem item;
privatestaticfinalUser user;
privateUserTableProvider userTableProvider;
static{
finalString username = "aUsersName";
item = newItem.withString( "username", username);
user = newUser(username);
}
@BeforeEach
publicvoidsetupTestFixture{
userTableProvider = newUserTableProvider;
}
@Test
publicvoidtest1{
assertEquals(
user,
userTableProvider.execute(item),
"Should convert a Dynamo SDK item to a User entity object.");
}
}
看到這些模式和代碼的重復了嗎?更多樣板!我們總是會創建一些final static測試數據,然后在每個測試前,我們都會創建一個待測試單元的新實例。最后,測試運行單元的execute方法,并斷言結果是否符合我們的期望。UnitilyLang針對該模式進行了優化,我們只需定義需要創建的單元、測試數據和斷言即可。因此,上述兩段代碼(1286個字符)可以替換為(484個字符):
testdata
username = "aUsersName";
user= new User(username);
message= "aUsersName has 10 letters in his/her name";
unit
UserMessageCreator
asserts
"should create a message containing the number of characters in the username"user -> message
testdata
username = "aUsersName";
item= new Item.withString( "username", username);
user= new User(username);
unit
ItemToUserConverter
asserts
"Should convert a Dynamo SDK item to a User entity object."item -> user
如何測試依賴關系會導致副作用的單元?例如DynamoDbUserProvider等。這與測試純單元類似,只不過我們需要模擬會引起副作用的依賴項。
publicclassDynamoDbUserProviderTest{
privatestaticfinalItem item;
privatestaticfinalUser user;
privatestaticfinalString userId;
privatestaticfinalDynamoTable table;
privatestaticfinalConfig config;
privateConfigProvider configProvider;
privateDynamoItemReader dynamoReader;
privateDynamoDbUserProvider dynamoDbUserProvider;
static{
finalString username = "aUsersName";
item = newItem.withString( "username", username);
user = newUser(username);
userId = "aUserId";
table = newDynamoTable( "region", "table", "idField");
config = newConfig(table, null);
}
@BeforeEach
publicvoidsetupTestFixture{
configProvider = mock(ConfigProvider.class);
dynamoReader = mock(DynamoItemReader.class);
dynamoDbUserProvider =
newDynamoDbUserProvider(
configProvider, newUserTableProvider, dynamoReader, newItemToUserConverter);
}
@Test
publicvoidtest1{
when(configProvider.execute).thenReturn(config);
when(dynamoReader.execute(table, userId)).thenReturn(item);
assertEquals(
user,
dynamoDbUserProvider.execute(userId),
"Should return a user created from the Item returned from Dynamo DB.");
}
}
讓我們比較一下UsernameLetterCountPublisher的測試:
publicclassUsernameLetterCountPublisherTest{
privatestaticfinalString userId;
privatestaticfinalUser user;
privatestaticfinalConfig config;
privatestaticfinalString message;
privateConfigProvider configProvider;
privateUserProvider userProvider;
privatePublisher publisher;
privateUsernameLetterCountPublisher usernameLetterCountPublisher;
static{
userId = "aUserId";
user = newUser( "aUsersName");
config = newConfig( null, userId);
message = "aUsersName has 10 letters in his/her name";
}
@BeforeEach
publicvoidsetupTestFixture{
configProvider = mock(ConfigProvider.class);
userProvider = mock(UserProvider.class);
publisher = mock(Publisher.class);
usernameLetterCountPublisher =
newUsernameLetterCountPublisher(
configProvider,
newUserIdProvider,
userProvider,
newUserMessageCreator,
publisher);
}
@Test
publicvoidtest1{
when(configProvider.execute).thenReturn(config);
when(userProvider.execute(userId)).thenReturn(user);
usernameLetterCountPublisher.execute;
verify(publisher).execute(message);
}
}
還是相同的模式、相同的復制、相同的模板。只不過現在我們使用模擬創建單元并在測試中設置這些模擬。我們可以用756個UnitilyLang字符替換上述2605個字符:
testdata
username = "aUsersName";
item = newItem.withString( "username", username);
user = newUser(username);
userId = "aUserId";
table = newDynamoTable( "region", "table", "idField");
config = newConfig(table, null);
unit
DynamoDbUserProvider
-> config
SubConfigProvider Config usersTable
userId -> item
ItemToUserConverter
asserts
"Should return a user created from the Item returned from Dynamo DB."userId -> user
testdata
userId = "aUserId";
user = newUser( "aUsersName");
config = newConfig( null, userId);
message = "aUsersName has 10 letters in his/her name";
unit
UsernameLetterCountPublisher
-> config
SubConfigProvider Config userId
userId -> user
UserMessageCreator
message ->
-- No asserts so any dependencies without outputs will be verified
正如我們在本文中所見,UnitilyLang更加簡潔,但可能沒有那么透明。我們可以通過繪制圖片,兼顧兩者。下面是我們繪制的DynamoDbUserProviderTest:
以及UsernameLetterCountPublisherTest:
帶有單位名稱的灰色框表示具體的依賴關系,虛線框表示模擬,標簽定義了模擬期望并返回的測試數據。
運行程序
現在,代碼已經寫好了,而且我們也通過測試驗證了代碼。我們需要的最后一樣東西就是程序。在Java中,我們需要創建一個main函數,在其中創建根單元(及其所有依賴項)并執行它。
publicclassMain{
publicstaticvoidmain(String[] args){
newUsernameLetterCountPublisher(
newConfigProvider( newEnvironmentProvider),
newUserIdProvider,
newUserProvider {
privatefinalDynamoDbUserProvider dynamoDbUserProvider =
newDynamoDbUserProvider(
newConfigProvider( newEnvironmentProvider),
newUserTableProvider,
newDynamoItemReader( newDynamoClientProvider),
newItemToUserConverter);
publicUser execute( finalString id) {
dynamoDbUserProvider.execute(id);
}
},
newUserMessageCreator,
newPublisher {
privatefinalConsolePrinter consolePrinter = newConsolePrinter;
publicvoidexecute( finalString x) {
consolePrinter.execute(x);
}
})
.execute;
}
}
我們又一次看到,這段代碼與我們為所有其他應用程序編寫的代碼完全相同。我們可能會使用DI框架來創建單元,但是這種方法對于小型應用程序是完全有效的。此處唯一真正有意思的是,我們創建了接口的匿名實現,例如new UserProvider,這意味著我們可以使用任何類來實現接口,而無需使用標簽implements。例如,它更類似于go或Type,這兩種語言的對象默認就實現了接口。當然,這意味著代碼中存在一些未經測試的邏輯,但是我相信自己(或更重要的是IDE)可以做到這一點。
在UnitilyLang中,我們只需按照如下方式聲明主入口點:
mainUsernameLetterCountPublisher
DynamoDbUserProvider
ConfigProvider Config
SubConfigProvider Config userTable
DynamoItemReader
ItemToUserConverter
UsernameLetterCountPublisher
ConfigProvider Config
SubConfigProvider Config userId
DynamoDbUserProvider
UserMessageCreator
ConsolePrinter
或者,我們可以這樣畫出來:
總結
本文編寫代碼、構建腳本等的活動就到此為止,因為這些活動只會產生更多的樣本。我們在文末附上了完整的UnitilyLang代碼。總共2106個字符。而Java代碼庫的總長度為22084個字符。也就是說,我們只用10%的代碼就可以在UnitilyLang中構建相同的項目。
UnitilyLang減少了我們所需編寫的代碼量,UnitilyLang的圖示簡化了我們的項目。想一想我們在本文中花費了多少時間來編寫Java代碼,然后再將這個時間量與繪制幾個方框所需的時間進行比較。想一想在了解應用程序的時候,如果我們只需查看上述的一張圖片,而非大量的Java代碼庫,那么該有多么容易。
最后,我想以一個大反轉來結束本文:我并沒有編寫本文所示的任何Java代碼,這些代碼都是根據UnitilyLang圖片生成的,具體做法請參照視頻鏈接:https://youtu.be/b2NrD-e89PU。
UnitilyLang是一款根據圖片定義生成代碼的應用程序。你來繪制圖片,它來生成代碼。圖片越是直觀,就越易于創建和重構代碼。我之前說重構Java代碼就像在圖片中重新繪制箭頭一樣簡單。Unitliy就可以完成這樣的操作。如果想提取依賴項,只需畫一個框并勾上箭頭。
你可以仔細閱讀github上(https://github/rowland-street/boilerplate-demo)上的生成項目,并將其與下面的UnitilyLang代碼進行比較。
完整的UnitilyLang代碼
-- Units
data User
username: String
data Config
table: DynamoTable
userId: String
pure ItemToUserConverter: Item -> User
item -> new User(item.getString( "name"))
workflow DynamoDbUserStore
= 4. ( 3( 21))
pure UserMessageCreator: User -> String
user -> String.format( "%s has %d letters in his/her name", user.username, user.username.length)
sideeffect ConsolePrinter: String ->
message -> = System.out.println(message)
workflow UsernameLetterCountPublisher
= 5( 4( 3( 21)))
-- Tests
testdata
username = "aUsersName";
user = newUser(username);
message = "aUsersName has 10 letters in his/her name";
unit
UserMessageCreator
asserts
"should create a message containing the number of characters in the username"user -> message
testdata
username = "aUsersName";
item = newItem.withString( "username", username);
user = newUser(username);
unit
ItemToUserConverter
asserts
"Should convert a Dynamo SDK item to a User entity object."item -> user
testdata
username = "aUsersName";
item = newItem.withString( "username", username);
user = newUser(username);
userId = "aUserId";
table = newDynamoTable( "region", "table", "idField");
config = newConfig(table, null);
unit
DynamoDbUserProvider
-> config
SubConfigProvider Config usersTable
userId -> item
ItemToUserConverter
asserts
"Should return a user created from the Item returned from Dynamo DB."userId -> user
testdata
userId = "aUserId";
user = newUser( "aUsersName");
config = newConfig( null, userId);
message = "aUsersName has 10 letters in his/her name";
unit
UsernameLetterCountPublisher
-> config
SubConfigProvider Config userId
userId -> user
UserMessageCreator
message ->
-- App
main UsernameLetterCountPublisher
DynamoDbUserProvider
ConfigProvider Config
SubConfigProvider Config userTable
DynamoItemReader
ItemToUserConverter
UsernameLetterCountPublisher
ConfigProvider Config
SubConfigProvider Config userId
DynamoDbUserProvider
UserMessageCreator
ConsolePrinter
原文:https://unitily/articles/boilerplate.html
本文為 CSDN 翻譯,轉載請注明來源出處。
Python系列學習成長課來了!15年經驗專家、CSDN特級講師親自授課,還等什么?立即掃碼報名學習:
,參與有獎調查!


