你知道吗?90%的好代码都是......

时间:2020-02-11 来源:www.utprssa.com

returnObjects.equals(用户名、用户、用户名);

这段代码是所有模板。我们的应用程序没有具体的内容,只有一个名称,但是创建一个类是一项我们需要重复执行的任务,没有太大的变化,所以我认为这段代码是一个模型。

UsernameLetterCountPublisher依赖于从数据库中查找用户。我们希望最小化UsernameLetterCountPublisher类与其依赖项之间的耦合。这主要是因为通过这种方式,我们可以在没有实际数据库的情况下测试它,并减少将来更改该类的可能性。我们定义了一个接口,并将该接口的实例注入到UsernameLetterCountPublisher的构造函数中,如下所示:

public class usernameletrecoutpublisher {

privatefinaluser provider user provider;

PublicUserNameLettercountpublisher(finalUserProvider用户提供程序){

this . UserProvider=UserProvider;

returnObjects.equals(用户名、用户、用户名);

returnObjects.equals(用户名、用户、用户名);

通常,我们通过构造函数注入单元依赖关系,所以这段代码也是一个模板。我们定义的用户提供程序如下:

publicationfaceSerprovider {

user execute(finalstringid);

returnObjects.equals(用户名、用户、用户名);

还有用户数据类型:

公共类用户{

公共终结结构用户名;

publicUser(终结字符串用户名){

this.username=username

returnObjects.equals(用户名、用户、用户名);

Override

publicboolean等于(对象o) {

如果(此==o)返回true;

if(o==null|| getClass!=o.getClass)返回false;

用户用户=(用户)o;

returnObjects.equals(用户名、用户、用户名);

Override

returnObjects.equals(用户名、用户、用户名);

Override

returnObjects.equals(用户名、用户、用户名);

returnObjects.equals(用户名、用户、用户名);

返回“用户{”用户名=“用户名”}”;

this . UserTableProvider=UserTableProvider;

this . UserTableProvider=UserTableProvider;

您可能会说用户提供程序接口包含特定于应用程序的逻辑,但是我不认为通过标识查找实体是特定于应用程序的。我认为这里唯一的非模板代码(除了命名)是用户对象拥有的用户名字段。例如,为了确保不变性,用户类的定义包括公共final,而toString、equals和hashCode函数的定义对于所有数据结构都是相同的。

另一种方式:统一语言

接下来,让我们定义我们自己的编程语言,名为统一语言,它旨在为我们提供一种简洁的编程模式。在Java代码和UnitilyLang的实现之间有一对一的映射,但是后者要简单得多。这突出了Java方法中的样板代码。我们可以使用UnitilyLang替换Java代码的最后两个部分,只有30个字符(596个字符):

data User

username : String

data在上面的代码中表示这个类只有公共的最终字段,我们可以将equals、hashCode和toString函数应用到这些字段,并通过这个类的构造函数初始化它们。用户是此类的名称,用户名是其唯一的字符串类型字段。我们将在本文后面解释为什么不需要定义接口。

在下面,我们需要创建一个用户提供者接口的实现,它将从动态数据库(一个无SQL数据库,你不需要知道太多)中读取数据。

PublicClassDemandUserProvider {

PrivatefinalConfigProvider configProvider;

PrivatefinalUserTableProvider UserTableProvider;

privatefinalDynamoItemReader电动阅读器;

privatefinaliitemTouserconverter itemTouserconverter;

PublicDemandBuserProvider(

FinalConfigProvider config Provider,

FinalUserTableProvider UserTableProvider,

FinalDemandItemReader Demanreader,

FinalItemTourConverter ItemTourConverter){

this . config Provider=config Provider;

returnObjects.equals(用户名、用户、用户名);

this . GeoderLeader=GeoderLeader;

this . ItemTousserconverter=ItemTousserconverter;

FinalItem item=DynamiCS reader . execute(DynamiCS table,id);

publicUser execute(终结字符串id) {

终结配置配置=配置提供程序. execute

FinalDynamotable Dynamotable=UserTableProvider . execute(配置);

returnObjects.equals(用户名、用户、用户名);

returnObjects.equals(用户名、用户、用户名);

我们称DynamoDbUserProvider单元为工作流单元。工作流单元不需要执行任何操作,只是为了组成其依赖关系的执行。创建抽象时我们需要这样的工作流单元,例如我们可以有一个名为DynamoDbUserProvider的单元,其功能很明显,所以我们无需在意其内部工作原理

DynamoDbUserProvider单元拥有四个依赖项。为了避免本文涉及过多逻辑,我不打算展示电动阅读器的代码。我也没有展示ConfigProvider依赖的代码。它的实现通常为以下内容以下内容:读取环境变量或磁盘上的文件,并提供应用程序级的配置,这是另一个数据类。在我们的示例中,配置的代码具体如下:

PublicClassconfig {

PublicFinalDynamotable userTable;

Public FinalStrIng UserID

Public config(FinalDemanTotable UserTable,FinalString UserID){

此。用户表=用户表;

this . UserID=UserID

Override

public boolean等于(对象o) {

如果(此==o)返回真实;

if(o==null|| getClass!=o.getClass)返回假的;

配置配置=(配置)o .

Override

Override

返回"配置{ ' ' UserTable=' UserTable ' UserID=' UserID ' } ";

这个。区域=区域;

这个。区域=区域;

它还有一个电动工作台,我们在查找某个表时需要用到这个字段,这是另一个数据类:

Public class Demantable {

Public FinalString区域;

publicfinalString表名;

Public FinalString Idfield name

Public Dynamotable(终结字符串区域、终结字符串表名、终结字符串idFieldName){

Override

public boolean等于(对象o) {

如果(此==o)返回真实;

Override

public boolean等于(对象o) {

如果(此==o)返回真实;

if(o==null|| getClass!=o.getClass)返回假的;

对象。等于(IdFieldName,than。idfield name);

Override

public int hashCode {

return Objects。哈希(区域、表名、IDfield name);

',tableName=' '

Override

public StrIng to StrIng {

' region=' '

region

'

' ' '

',IdFieldName=' '

IdFieldName

}

}

上述电动数据库也需要依赖一个单元来提供我们所需的某一段配置:

PublicClassUserTableProvider {

PublicDynamotable execute(FinalConfig config){

返回配置。用户表;

还有一个项目转换器是为了将AWS DynamoDb SDK的结果(项目)转换成我们的数据类用户。

公共类项目用户转换器{

公共用户执行(最终项目项目){

字符串用户名=项目。GetString('用户名');

用户用户=新用户(用户名);

returnuser

}

}

这又是一段样本代码。我们构建的每一个应用都会重复使用ConfigProvider的实现,实际的配置会有所不同,但是创建配置的逻辑不会变,所以我认为这也是样板电动阅读器依赖项也是如此。最后,我们所有的服务类都通过构造函数注入了它们的依赖关系,并作为私人决赛字段保存到了我们所有的项目中

DynamoDbUserProvider中唯一涉及应用程序特定逻辑的就是命名,及其依赖项组合在一起的顺序,目的是为了通过编号获取用户。

如果使用统一梁的话,我们可以用(248个字符)替换前面的5段代码(隐藏在ConfigProvider和电动阅读器类中的3006个字符在统一梁中都没有必要)。

data GetOvertable

region : String

TableName : String

Idfield Name : String

要定义ItemToUserConverter,我们只能使用目标编程语言(这里是Java)。统一朗的设计目标是抽象、不变性和组合。除了写逻辑,它与语言无关。为了定义纯单元,我们需要编写一些代码。

workflow unit

workflow声明为通过构造函数注入的每个依赖项定义了一个带有私有最终字段的类。至少也定义了DynamoDbUserProvider的功能。如果这一段的后续内容不是特别清楚,那么请不要担心,因为我的写作能力不足,我们以后会更清楚地解释。接下来,让我们开始.每个数字n对应于第n个依赖项。报表4(3 (2 1))定义了这些依赖关系的组合。我们将第二个依赖应用于第一个依赖的结果。然后,部分应用第三个依赖项,第四个依赖项用于形成结果函数。这种声明函数的方式称为零点样式。

UnitilyLang可以用更少的代码表达相同的意思,仅仅是因为Java编程语言和库不能以最自然的方式表达我们遵循的简洁的代码模式。UnitilyLang可能更简洁,但仍然没有自然的方式来表达DynamoDbUserStore单元的工作,即形成其依赖关系的方式。前一段的自然语言描述显然没有清楚地解释这个问题。那么,什么是更自然的方式呢?他们说一张图片胜过千言万语,所以让我们画一张动态用户提供者的图片。

上述数字相当于4。(3 (2 1)),但更容易理解。我相信你能理解,所以我就不多解释了。

统一朗(及以上)工作流单元的有趣之处在于它们很常见。它们不会根据依赖类型而改变。只要实例化时每个箭头末端的类型相同,就可以编译它。这实际上限制了函数的功能,使推理更加容易。他们只能使用依赖执行的结果。这也意味着我们不需要声明接口,并且可以使用任何类型的正确依赖关系。

Java和uniilylang之间以及uniilylang和上图之间存在一对一的映射。上面的图片比你想象的要好得多。与代码相比,人类在理解和推理图片方面有着无限的优势。重构也容易得多。如果你想改变数据流的流动方式,只需删除一个箭头,并重新绘制指向另一个方向的箭头!

现在,我们已经写了足够多的代码,本文开头的UsernameLetterCountPublisher已经写好了。我们需要为它创建一个执行方法来查询数据库,计算字母并在屏幕上显示消息。

Public class usernameLettercountpublisher {

PrivatefinalConfigProvider config Provider;

PrivatefinalUserIdProvider UserIdProvider;

privatefinalUserProvider用户提供程序;

PrivatefinalUserMessageCreator UserMessageCreator;

privatefinalPublisher发布者;

PublicUserNameLettercountpublisher(

FinalConfigProvider config Provider,

FinalUserIdProvider UserIdProvider,

finalUserProvider userProvider,

FinalUserMessageCreator UserMessageCreator,

Finalpublisher publisher){

this . config Provider=config Provider;

this . UserIdProvider=UserIdProvider;

this . UserProvider=UserProvider;

this . UserMessageCreator=UserMessageCreator;

this.publisher=publisher

}

public void execute {

FinalConfig config=ConfigProvider . execute;

FinalStrIng id=USeridProvider . execute(配置);

FinalUSer user=USerProvider . execute(id);

finalString消息=userMessageCreator.execute(用户);

publisher.execute(消息);

}

}

让我们快速介绍它的依赖关系。我们重用了ConfigProvider并注入了一个userid提供程序:

public class useridprovider {

stringgetserid(application fig application fig){

return application fig . user id;

}

}

的目的是获取一部分配置,就像我们在加载userTableName时所做的那样。我们还注入了一个用户消息创建者类:

PublicClassUserMessageCreator {

PublicString Execute(最终用户){

String message=

String . format(“% s”在其名称中有%d个字母)、user.username、user . username . length);

returnmessage

}

}

这个单元是纯的,并且与其父单元的命名相关联,所以我不认为它需要通过更抽象的公共接口来引用。此外,还存在发布者依赖关系。这种依赖性不是很纯(它会影响屏幕上的输出)。我们想要使用各种实现,比如模拟测试,所以我们已经定义了一个需要注入的接口。

PublicInterfacePublisher {

void execute(final StrIng x);

user=newUser(用户名);

并且还创建了一个真正的应用程序实现:

Public class OnlLeprinter {

Public VoIceExecute(final StrIng x){

System . out . println(x);

user=newUser(用户名);

user=newUser(用户名);

我们认为这个通用名称最适合当前的类。如果我们选择发布到消息队列而不是控制台,发布器仍然有意义。接下来,我们可以注入消息队列发布器(而不是控制台队列),而无需对UsernameLetterCountPublisher类进行任何修改。正如您可能已经注意到的那样,我们没有使用Implements Publisher语句来标记ConsolePrinter类,也没有将此操作添加到用户提供程序接口的DynamoDbUserProvider实现中。在本文的最后,我们将解释为什么这个单元没有被实例化。

同样,大多数用户名字母计数发布者代码是一个模板。您可以通过比较DynamoDbUserProvider来确认这种重复模式。我们需要定义如何形成它的依赖关系(就像我们对DynamoDbUserProvider所做的那样),并且在UserMessageCreator和ConsolePrinter中有一些特定于应用程序的逻辑,但是这就是我们需要定义的。

因此,上述5个代码(1545个字符)可以用274个字符的单位长度替换:

PureUserMessageSesecretor : user-String

user-String . format(“% s”在其/hername中有% d个字母)、user.username、user . username . length)

sideffect console Printer : String-

message-system . out . println(message)

WorkflowUserNameLettercountPUBLISHER

5(4(3(21))

我们见过类似于UserMessageCreator的语句副作用声明类似于纯单元声明,但是我们可以知道这将导致副作用。

我们可以通过下图可视化用户名字母计数发布器。

单元测试和集成测试

到目前为止,我们已经编写了完成任务所需的所有代码。现在,我们需要编写一些测试和程序来运行代码。猜猜接下来会发生什么?是的,更多样本代码。

我们将从纯单元开始,下面是用户消息创建者的测试代码:

PublicClassUserMessageCretartest {

PrivateStaticFinalUser;

privatestaticfinalString消息;

privateUserMessageCreator用户消息创建者;

Public LyclassTouserconverter {

finalString用户名=' aUsersName

user=newUser(用户名);

message=' aUsersName的名字中有10个字母';

before EACH

user=newUser(用户名);

Public VaniqueSetUptestfixture {

UserMessageCreator=NewUserMessageCreator;

AserteQualis(

Test

public vouritest 1 {

user=newUser(用户名);

user=newUser(用户名);

userMessageCreator.execute(用户),

'应该创建一条包含用户名中字符数的消息');

static{

我们将从纯单元开始,下面是用户消息创建者的测试代码:

比较我们另一个纯单元项目的测试转换程序:

privatestaticfinalString消息;

privateUserMessageCreator用户消息创建者;

privatestaticfinalUser用户;

Public LyclassTouserconverter {

user=newUser(用户名);

message=' aUsersName的名字中有10个字母';

before EACH

user=newUser(用户名);

user=newUser(用户名);

Public VaniqueSetUptestfixture {

UserMessageCreator=NewUserMessageCreator;

AserteQualis(

UserTableProvider=NewUserTableProvider;

Test

user=newUser(用户名);

user=newUser(用户名);

AserteQualis(

User,

userTableProvider.execute(项),

用户=新用户(用户名);

message=' aUsersName的名字中有10个字母;

用户=新用户(用户名);

config=newConfig( null,UserID);

断言

应该创建一个包含用户名"用户消息

usernameLettercountpublisher。执行;

username="auserName "中字符数的消息;

用户=新用户(用户名);

用户=新用户(用户名);

item=newItem。顶住(“用户名,用户名);

断言

DynodBuserprovider

'应该将Dynamo SDK项转换为用户实体对象。项目-用户

如何测试依赖关系会导致副作用的单元?例如DynamoDbUserProvider等。这与测试纯单元类似,只不过我们需要模拟会引起副作用的依赖项

PublicClassDemanduserProvidertest {

PrivateStaticFinalitem项目;

privatestaticfinalUser用户;

privatestaticfinalString用户标识;

privatestaticfinalDynamoTable表;

privatestaticfinalConfig配置;

privateConfigProvider配置提供程序;

privateDynamoItemReader电动阅读器;

PrivateDemanduserProvider DemanduserProvider;

privateUserProvider用户提供程序;

finalString用户名=' aUsersName

item=newItem。顶住(“用户名,用户名);

user=newUser(用户名);

UserID=' AuSirid

table=NewDynotertable(' region ',' table ',' IDfield ');

config=newConfig(表,空);

NewDynodBuserprovider(

在EACH

Public VaniqueSetptestFixture之前{

config Provider=mock(配置提供程序。类);

发电机阅读器=模拟(发电机阅读器。类);

DynodbUserProvider=

config=newConfig(表,空);

configProvider,newUserTableProvider,DynodBuserprovider,NewItemTouserconverter);

user,

Test

public vouritest 1 {

when(配置提供程序)。执行).然后返回(配置);

when(GeoderReader)。执行(表,用户标识).返回(项目);

AserteQualis(

config=newConfig(表,空);

config=newConfig(表,空);

'应该返回从dynododdb返回的项目创建的用户);

USer=NewUSer(' AusErname ');

privatestaticfinalUser用户;

PrivateStaticFinalitem项目;

privatestaticfinalDynamoTable表;

PrivateStaticFinaliring UserID;

privatestaticfinalConfig配置;

privatestaticfinalConfig配置;

privatestaticfinalString消息;

privateConfigProvider配置提供程序;

PrivateDemanduserProvider DemanduserProvider;

user=newUser(用户名);

PrivateUsernameLettercountpublisher UsernameLettercountpublisher;

static {

message=' aUsersName的名字中有10个字母;

config=newConfig(表,空);

NewDynodBuserprovider(

在EACH

在EACH

Public VaniqueSetptestFixture之前{

config Provider=mock(配置提供程序。类);

UserProvider=mock(UserProvider。类);

publisher=mock(publisher。类);

UserNameLettercountpublisher=

NewUserNameLettercountpublisher(

config Provider,

config=newConfig(表,空);

configProvider,newUserTableProvider,DynodBuserprovider,NewItemTouserconverter);

user,

public vouritest 1 {

Test

config=newConfig(表,空);

config=newConfig(表,空);

when(用户提供程序。执行(用户标识).然后返回(用户);

应该创建一个包含用户名"用户消息

usernameLettercountpublisher。执行;

finalString用户名=' aUsersName

item=newItem。顶住(“用户名,用户名);

user=newUser(用户名);

UserID=' AuSirid

table=NewDynotertable(' region ',' table ',' IDfield ');

用户=新用户(用户名);

user=newUser(用户名);

UserID=' AuSirid

table=NewDynotertable(' region ',' table ',' IDfield ');

config=newConfig(表,空);

item=newItem。顶住(“用户名,用户名);

断言

应该创建一个包含用户名"用户消息

user=newUser(用户名);

PrivateUsernameLettercountpublisher UsernameLettercountpublisher;

static {

message=' aUsersName的名字中有10个字母;

用户=新用户(用户名);

'应返回从dynododdb返回的项创建的用户UserID-user

UserID=' AuSirid

UserID=' AuSiriD ';

USer=NEwUSer(' AusErname ');

config=newConfig( null,UserID);

message=' aUsersName的名字中有10个字母;

unit

正如我们在本文中看到的,UnitilyLang更简洁,但可能不那么透明。我们可以通过画画来兼顾两者。以下是我们绘制的dynamidbuser providerdest:

和usernameleterrountpublishertest:

图。带有单元名称的灰色框表示特定的依赖关系,虚线框表示模拟,标签定义模拟期望并返回的测试数据。

运行程序

现在,代码已经编写完毕,我们已经通过测试验证了代码。我们最不需要的是一个程序。在Java中,我们需要创建一个主要函数来创建根单元(及其所有依赖项)并执行它。

Public class main {

Public staticulvamain(字符串[)参数){

NewUserNameLettercountpublisher(

NewConfigProvider(新环境提供程序),

newUserIdProvider,

NewUserProvider {

PrivateFinalDemandUserProvider DwUserProvider=

NewDemandUserProvider(

NewConfigProvider(新环境提供程序),

newUserTableProvider,

newDynamoItemReader(新newDynamoClientProvider),

newItemToUserConverter

publicUser execute(终结字符串id){

DynodbUSerProvider . execute(id);

}

},

newUserMessageCreator,

Newpublisher {

PrivatefinalconsolerPrint consolerPrint=NewconsolerPrint;

public void execute(FinalStrIng x){

console print . execute(x);

}

})

。执行;

}

}

我们再次看到,这段代码与我们为所有其他应用程序编写的代码完全相同。我们可以使用直接投资框架来创建单元,但是这种方法对于小应用程序是完全有效的。这里真正有趣的是,我们已经创建了接口的匿名实现,例如新的用户提供程序(UserProvider),这意味着我们可以使用任何类来实现接口,而无需使用标签实现。例如,go或Type更相似,这两种语言中的对象默认实现接口。当然,这意味着代码中有一些未经测试的逻辑,但是我相信我(或者更重要的是,集成开发环境)能够做到。

在UnitilyLang中,我们需要做的就是将主要入口点声明如下:

MainUsername selected countProblers

DynamidUserProvider

ConfigProvider Config

SubConfigProvider Provider Config

DynamidItem ader

ItemTouserconverter

UserNameLettercountpublisher

ConfigProvider Config

SubConfigProvider Config UserId

DynamidUserProvider

UserMessagEcreator

console Printer

或者,我们可以这样绘制它:

Summary

编写代码、构建脚本、创建脚本本文已经结束,因为这些活动只会产生更多的样本。在本文的最后,我们附上了完整的统一朗代码。总共2106个字符。Java代码库的总长度是22,084个字符。换句话说,我们只需要10%的代码就可以在统一的地方构建同一个项目。

unitylang减少了我们需要编写的代码量,unitylang的图表简化了我们的项目。想想我们在本文中花了多少时间编写Java代码,然后将这段时间与绘制几个框所需的时间进行比较。想想当我们理解应用程序时,如果我们只需要看上面的一张图片,而不是看大量的Java代码库,会有多容易。

最后,我想以一个大逆转来结束这篇文章:我还没有编写本文中显示的任何Java代码。这些代码是基于单幅图片生成的。详情请参考视频链接:

UnitilyLang是一个基于图片定义生成代码的应用程序。你画画,它就会产生代码。图片越直观,创建和重构代码就越容易。我之前说过重构Java代码就像在图片中重新绘制箭头一样简单。Unitliy可以做到。如果您想选择依赖项,只需画一个框并勾选箭头。

您可以仔细阅读github()上生成的项目,并将其与下面的UnitilyLang代码进行比较。

完整的统一长度代码

-units

data User

username 3336 string

data config

table 3336 dynamic table

User id 3336 string

puritemtouserconverter : Item-User

Item-new User(Item . GetString(' name '))

workflow dynamic db UserStore

=4。(3( 21))

纯用户消息创建器:用户-字符串

用户-字符串。格式(“%s”在他/她的名字、用户、用户名、用户名。长度中有%d个字母)

副作用控制台:字符串-

message-=系统。出去。println(消息)

workflow usernameLettercountpublisher

=5(4(3(21))

-Tests

username=' AusErname ';

user=newUser(用户名);

message=' aUsersName的名字中有10个字母;

item=newItem。顶住(“用户名,用户名);

usernameLettercountpublisher

断言

-Tests

SubConfigprovider Config UserStable

username=' AusErname ';

message=' aUsersName的名字中有10个字母;

user=newUser(用户名);

usernameLettercountpublisher

ItemToussConverter

-Tests

SubConfigprovider Config UserStable

username=' AusErname ';

item=newItem。顶住(“用户名,用户名);

user=newUser(用户名);

UserID=' AuSirid

message=' aUsersName的名字中有10个字母;

config=newConfig(表,空);

UserID=' AuSiriD ';

DynodBuserprovider

user=newUser(用户名);

usernameLettercountpublisher

UserId-Item

-Tests

item=newItem。顶住(“用户名,用户名);

'应返回从dynododdb返回的项创建的用户UserID-user

user=newUser(用户名);

message=' aUsersName的名字中有10个字母;

USer=NEwUSer(' AusErname ');

UserID=' AuSiriD ';

message=' aUsersName的名字中有10个字母;

unit

item=newItem。顶住(“用户名,用户名);

-Config

SubConfigProvider Config UserId

config=newConfig(表,空);

ItemTousserConverter

message-

-App

user=newUser(用户名);

USer=NEwUSer(' AusErname ');

ItemTousserConverter

message=' aUsersName的名字中有10个字母;

config=newConfig(表,空);

item=newItem。顶住(“用户名,用户名);

UserName Lettercountpublisher

Config Provider Config

SubConfigProvider Config UserId

Dynamid原文:统一谎言。请访问。html

本文为CSDN翻译,转载请注明来源出处