
我在地下室里挂了一个出气筒,在上面贴着一位典型经理的照片,然后把演讲者塞进里面玩一些让我生气的短语。 例如,一个梨说:“业务不需要您的完美代码。 他需要解决问题,以使利润涵盖成本。 如果您需要govnokod,那么就会有govnokod。” 我开始拥抱。
最近,我在梨子上加了一条字:“类型很困难,没有必要。” 这时,我打得很厉害,手几乎断了。 因为我足够了。 几个月前,我经历了职业生涯中最严重的案例之一。
我的朋友安托卡(Antokha)要求我为一家大型公司提供解决方案。 我同意了,我们陷入了公司荒谬,紧缩,与不可理解的同事之间的战争以及各种不公正的深渊。 我们什么也不能说,因此我们将讨论类型,以使此类垃圾永远不会重复出现。
1个
-(Antokha rcanedu )我受委托开发了一个内部平台工具-一个以面向对象模型的形式表示软件实体并与API服务统一工作的库。 即,一种用于与从源传播到显示器,反之亦然的数据进行交互的工具。
这样,大量的格式化,转换,计算和转换。 庞大的结构,复杂的层次结构,万物之间的无数联系。 在这样的网络中,很容易迷路。 您看到的是数据,却不知道可以做什么和不能做什么。 花很多时间来解决它。 这是很容易上瘾的。 只有很好地描述类型才能解决问题。
由于许多解决方案中缺少合适的类型,因此在运行时和编译时实现相同的程序行为变得更加困难。 类型应该完全确保所有内容都是相同的,并且在执行期间也会发生。 测试可以做同样的事情。 最好两者都依靠。 但是,如果您在类型和测试之间进行选择-类型会更可靠,更便宜。
当您成为前台时,有两个问题的根源-用户和后端。 对用户而言,一切都很简单-有许多方便的库和框架从用户I / O中抽象出来(反应,角度,Vue等)。
与后端交互还有另一个故事。 它的种类很多,而实现却是黑暗。 您无法定义一种描述数据的标准方法。 因此,发明了诸如“数据结构规范化”之类的拐杖,当所有传入数据都简化为刚性结构时,如果出现问题,则异常或异常操作就会开始。 这样可以加快并简化开发,但是实际上,它需要大量文档,UML图和功能描述。
由于前端已经成熟,因此在客户端和服务器部分的交互中出现了体系结构问题。 它变成了成熟的业务,而不仅仅是后端之上的布局。 以前,只有服务器端开发人员要求客户端-服务器交互。 现在,正反双方被迫谈判和密切合作。 我们需要一个工具,该工具将使我们能够与API数据源服务一起构建工作,避免丢失数据真实性,同时简化进一步的转换。 整个社区必须解决这个问题。
-(Phil)如果您是后端用户,则有很多针对该应用程序的成人解决方案和数据建模实践。 例如,在C#中,您具有数据模型类。 您需要一些EntityFramework,它为您提供了用来覆盖模型的属性。 您告诉Lib如何到达基地。 然后,您可以使用其界面来处理此数据。 这个东西叫做ORM。
在前端,我们并没有决定如何做到最好-我们搜索,尝试,撰写荒谬的文章,然后我们驳斥一切,重新开始,我们仍然不会做出一个决定。
-(Antoha)我之前写的所有内容都有一个大问题-狭义的专业化。 每次从头开始开发该库,并且每次针对一种客户端-服务器交互来对其进行优化。 所有这些都是由于缺少适当的键入而发生的。
我相信,如果没有静态输入,就很难想象有一个通用的库可以使用API和域表达式。 其中会有很多反思,它将包含大量文档,并且将被不同的应用程序围绕,并指明一种或另一种形式的实践。 这不是简化。
一个好的通用工具应该可以在任何切片上提供完整的数据图,以便您始终准确地知道如何使用该数据以及为什么需要它。
-(Phil)我们需要一个库,使我们能够为每个实体提供详细的描述和控制,以便从具有不同接口的不同资源(从REST API和json-rpc到graphQL和NQL)获取该实体的数据。 这将使不断增长的代码库和结构保持严格和有序。 使用简单直观。 随时提供完整,准确的实体状态描述。 我们想尽可能地从数据层抽象用户的模块。
首先,我们看一下现有的。 我们什么都不喜欢。 出于某种原因,所有用于处理数据的库都是在js上创建的,或者都是由js制成的。 这些都破坏了一切。 他们对开发人员视而不见,说这样的库对您没有多大帮助,您将无法浏览数据的类型,也将无法表达他们的联系。 当使用不同类型的多个API或异构API时,情况会变得更糟。
所有这些库都没有受到类型的充分保护。 因此,它们产生的问题多于解决的问题。 不使用它们,而是根据您的特定领域做出决定会更容易。
因此,我们决定创建一个更强大,更抽象的库-适用于所有事物,而不是紧握任务的狭lib库,并且我们坚信自己的正确性,因为只有这样,才能创造出真正的好东西。
2
-(Antoha)通常,我正在等待各种访问权限,这样我就可以进入公司资源库。 这可能持续数周。 就我而言,只用了一个。 这时,根据我在创建类似库中的经验,我分解了任务,估计了时间表。
问题是,在像其他所有人一样,我做出非常专业的决定之前。 使用通用工具会带来其他问题。 类型系统非常复杂,我和其他任何人都没有设计经验。 更糟糕的是,我周围的开发人员根本不了解静态类型。
但是我开始做我认为正确的事。 在日报上,我告诉您我在做什么以及为什么这样做,但是原则上没有人了解我。 我向团队提出的有关该问题的问题始终没有得到解答。 好像我不存在。 杜德(Dude),他做的事情非常复杂和难以理解。
我知道javaScript不能以任何方式在这里工作。 我需要一个具有强大的输入模型,与javaScript的出色交互,具有大型社区和严肃的生态系统的PL。
-(Phil)我已经等了很长时间了,Antoha才了解TypeScript的魅力。
-(Antoha)但是其中有许多问题使您发疯。 有输入,但是在程序的执行和用户的执行之间仍然没有完美的对应关系。 一开始打字稿似乎很复杂。 他必须忍受。 您总是想拿东西或扔东西。 逐渐地,您深入研究了语言,进入了语言的类型系统,在这里开始发生了一件有趣的事情。 它变得神奇。 一切都可以键入。
-(Phil)我们有生以来第一次在一起,开始了一些事情。
第一步是让用户描述数据方案。 我们想知道它的外观。 像这样:
type CustomerSchema = {
id: number;
name: string;
}
const Customer = new Model<CustomerSchema>(‘Customer’);
, , . id, , , .
, . , , -. , , .
, . — - , . , , . : , , . . :
/**
name: String
String - js -: StringConstructor
*/
const customerSchema = Schema.create({
id: Number,
name: String,
});
. , , . , Number String . : , . :
const customerSchema = Schema.create({
id: 1,
name: 2,
});
. `Schema.create` , . `if (!(property instanceof String)) throw new Error(« , »)`. .
-, , -, . , .
, , .
. , Schema.create.
:
//
type Map<T> = {
[key: string]: T | Map<T>;
};
/**
,
,
.
*/
type NumberType = Template<NumberConstructor, number, 'number'>;
type StringType = Template<StringConstructor, string, 'string'>;
type SymbolType = Template<SymbolConstructor, symbol, 'symbol'>;
type BooleanType = Template<BooleanConstructor, boolean, 'boolean'>;
type DateType = Template<DateConstructor, Date, 'date'>;
interface ArrayType extends Array<ExtractTypeValues<Types>> {};
type Types = {
Number: NumberType;
String: StringType;
Symbol: SymbolType;
Boolean: BooleanType;
Date: DateType;
Array: ArrayType;
};
//
type MapTypes= Map<ApplyTemplate<Types>>;
// -
type Declaration = ExtractInputTypes<MapTypes>;
interface Schema<...> {
// , .
create: <T extends Declaration>(
declaration: ConvertInstanceTypesToConstructorTypes<T>
) => Schema<T>;
};
, . , , , , .
,type CustomerSchema = {
id: number;
name: string;
};
const customerSchema: CustomerSchema = Schema.create({
id: Number,
name: String,
});
. , , .
. any. — any, 100%, , .
, , , . . `Schema.create` . 99% , . , . — ! , .
. , , , . . :
const customerSchema = Schema.create({
id: Number,
name: String,
});
// vscode : id, name
Schema.raw(customerSchema).
//
// .
Schema.raw(customerSchema).id;
// .
Schema.raw(customerSchema).neId;
. :
const customerSchema = Schema.create({
id: Number,
name: String,
});
if (true) {
customerSchema.add({gender: String});
}
// ,
// gender.
// ,
// .
Schema.raw(customerSchema).
, , , . gender, , (, , this !). - . , , .
, . , , . , . .
:
const customerSchema = Schema.create({
id: Number,
name: String,
});
// customerSchema.add({gender: String});
// , .
// :
const customerWithGenderSchema = customerSchema.add({gender: String});
// .
// :
Schema.raw(customerWithGenderSchema).
// id, name, gender
//
Schema.raw(customerSchema).
// id, name
, , . , .
:
const customerSchema = Schema.create({
id: Number,
name: String,
});
const repository = RepositoryManager.create(openApiDriver, {
// config
});
const Customer = Model.create(repository, customerSchema);
Customer.getAll().first().
// ide , id, name gender.
// ,
Customer.getAll().first().age;
// . , ,
// .
getAll .
:
type MapSchemaToDriver<S extends Schema, D extends Driver> =
InferSchemaDeclaration<S> extends SchemaDeclaration
? InferDriverMethods<D> extends DriverTemplate<IMRReader, IMRWriter>
? Repository<InferSchemaDeclaration<S>, InferDriverMethods<D>>
: never
: never;
interface Repository<D extends Driver, S extends Schema> {
get: <T extends DSLTerm>(dsl: ParseDSLTerm<T>) => MapSchemaToDriver<S, D> extends RepositoryTemplate ? ApplyDSLTerm<MapSchemaToDriver<S, D>, T> : Error<’type’, ‘Type error’>;
}
, :
«, B, A, , , , A. . - ».
. .
«» , , . , .
. , . :
« , *--.*, . , id . - , , ».
.
2.5
, , , . .
, — , , , . . , , .
, . , , , , . , , .
ODM ORM — IMR (Isomorphic Model Representation)
, , API . , . , select-where , .
— , , .
. . . , , , . , , , , .
, — , — , - .
.
3
— () , , . , , , . . , , , .
- , , « ». , . , — . , , ,
, .
. , , . , , , . . . , . , , , , .
— () , , . . , , , .
, , . . , .
— () , . , , . . , . - , , , , .
, - . , . , .
, , . , . .
4
— () , — . ! ?! , , , , , ODM/ORM - , . , . , , «- , ».
, , . . , , . .
, , - , . — , .
, . -:
/*
.
— .
.
*/
import { View } from '@view';
import { Logger } from '@utils';
// — , .
// .
import { Robot } from '@domain/models';
// ,
//
function foo() {
Robot.fetch({
location: {
area: 2,
free: true,
key: 'f49a6712', // - compile-time checked
}
})
.then(View.Grid.display)
.catch(Logger.error);
}
, . , — js/ts . , . - - , , , — Result .
. - Logger.Error, .
/*
:
,
- .
-
.
:
*/
import { Schema, Model } from 'imr';
import { Logger } from '@utils';
import { View } from '@view';
import { robotSchema } from '@domain/schemas';
import { batteryStationService } from '@domain/infrastructure/services/batteryStation';
import { Robot } from '@domain/models';
import { batteryNode } from '../services/nodeNames';
//
// , , ,
// , «»
const robotSchemaWithBattery =
robotSchema
.add('battery', Schema.union('li-ion', 'silicon'))
.remove('speed');
// ,
// :
function foo(nodeName) {
// -: -,
if (nodeName === batteryNode) {
// ,
const CustomerRobot = Model.create(robotSchemaWithBattery, batteryStationService);
CustomerRobot
// .
// , , 'li-notIon'
.fetch({ filter: { battery: 'li-ion' } })
// .
// , , , .
// , ,
// , .
.then(View.Grid.display)
.catch(Logger.error)
} else {
Robot
.fetch()
.then(View.Grid.display)
.catch(Logger.error)
}
}
5
— () , , , - , . , - , , .
.
, , — . . , — , , . . , , — , .
, : . . .
— . , .
: rcanedu, arttom