Nous avons écrit le code le plus utile de notre vie, mais nous l'avons jeté à la poubelle. Avec nous



J'ai accroché un sac de boxe dans mon sous-sol, y ai collé une photo d'un manager typique et j'ai fourré le haut-parleur à l'intérieur pour jouer des phrases qui me mettent en colère. Par exemple, une poire dit: «Les entreprises n'ont pas besoin de votre code parfait. Il doit résoudre le problème pour que le profit couvre les coûts. Si vous avez besoin de govnokod pour cela, alors il y aura govnokod. " Et je commence à me câliner.

Récemment, j'ai ajouté une note à la poire: "Les types sont difficiles et inutiles." En ce moment, j'ai frappé si fort que ma main se brise presque. Parce que assez de moi. Il y a quelques mois, j'ai vécu l'un des cas les plus flagrants de ma carrière.

Mon ami Antokha m'a demandé d'aider avec une solution pour une grande, grande entreprise. J'ai accepté et nous avons plongé dans l'abîme sans fond de l'absurdité des entreprises, du resserrement, de la guerre avec des collègues incompréhensibles et de toutes sortes d'injustices. Nous ne pouvons rien dire, nous allons donc parler de types afin que ces ordures ne se répètent jamais avec personne.

1


- (Antokha rcanedu ) J'ai été chargé du développement d'un outil de plateforme interne - une bibliothèque pour exprimer des entités logicielles sous forme de modèles orientés objet et pour le travail uniforme avec les services API. C'est-à-dire, un outil pour interagir avec les données qui voyagent de la source à l'affichage et vice versa.

De cette façon, une énorme quantité de formatage, de conversion, de calcul et de transformation. D'énormes structures, des hiérarchies complexes, de nombreuses connexions de tout avec tout. Dans de tels réseaux, il est très facile de se perdre. Vous voyez des éléments de données et vous ne savez pas ce qui peut être fait et ce qui ne peut pas être fait. Passez beaucoup de temps à le découvrir. C'est très addictif. Seule une bonne description des types résout le problème.

En raison du manque de typage approprié dans de nombreuses solutions, il devient plus difficile d'obtenir le même comportement de programme au moment de l'exécution et au moment de la compilation. Les types doivent garantir que tout est identique et se produira également pendant l'exécution. Les tests peuvent faire de même. Il est préférable de s'appuyer sur les deux. Mais si vous choisissez entre les types et les tests, les types sont beaucoup plus fiables et moins chers.

Lorsque vous créez un front, il existe deux sources de problèmes: l'utilisateur et le backend. Tout est simple avec l'utilisateur - il existe de nombreuses bibliothèques et frameworks pratiques abstraits des E / S utilisateur (react, angular, vue et autres).

Il y a une autre histoire en interaction avec le backend. Ses espèces sont nombreuses et les réalisations sont l'obscurité. Vous ne pouvez pas définir une approche standard pour décrire les données. Pour cette raison, des béquilles comme la «normalisation de la structure des données» ont été inventées, lorsque toutes les données entrantes sont réduites à une structure rigide, et si quelque chose se passe mal, des exceptions ou un fonctionnement anormal commencent. Cela devrait accélérer et simplifier le développement, mais en fait, cela nécessite beaucoup de documentation, des diagrammes UML, une description des fonctionnalités.

Un problème architectural dans l'interaction des parties client et serveur est survenu parce que le frontend avait mûri. C'est devenu une entreprise à part entière, et pas seulement une mise en page au-dessus du backend. Auparavant, seuls les développeurs côté serveur demandaient l'interaction client-serveur. Maintenant, les fronts et les dos sont obligés de négocier et de travailler en étroite collaboration. Nous avons besoin d'un outil qui nous permettra de structurer le travail avec les services de source de données API, pour éviter la perte de vérité des données et en même temps simplifier les transformations ultérieures. Ce problème doit être traité par l'ensemble de la communauté.

- (Phil) Si vous êtes un back-end, vous avez beaucoup de solutions pour adultes et de pratiques de modélisation de données pour l'application. Par exemple, en C #, vous disposez d'une classe de modèle de données. Vous prenez quelque chose, un EntityFramework, il vous fournit les attributs avec lesquels vous enduisez vos modèles. Vous dites à Lib comment atteindre la base. Ensuite, vous utilisez son interface pour manipuler ces données. Cette chose s'appelle ORM.

Dans le front-end, nous n'avons pas décidé de la meilleure façon de procéder: nous recherchons, essayons, écrivons des articles ridicules, puis nous réfutons tout, nous recommençons et nous ne parviendrons toujours pas à une seule décision.

- (Antoha) Tout ce que j'ai écrit auparavant avait un gros problème - une spécialisation étroite. Chaque fois que la bibliothèque a été développée à partir de zéro et chaque fois qu'elle a été affinée pour un type d'interaction client-serveur. Tout cela se produit en raison du manque de frappe appropriée.

Je pense que sans saisie statique, il est difficile d'imaginer une bibliothèque universelle pour travailler avec des API et des expressions de domaine. Il y aura beaucoup de réflexion, il contiendra une énorme quantité de documentation, il sera entouré de différentes applications avec une indication des pratiques pour l'une ou l'autre forme. Ce n'est pas une simplification.

Un bon outil universel de ce type devrait donner une image complète des données sur n'importe quelle tranche, afin que vous sachiez toujours exactement comment travailler avec ces données et pourquoi elles sont nécessaires.

- (Phil) Nous avons besoin d'une bibliothèque qui nous permettra de fournir une description détaillée et le contrôle de chaque entité, d'obtenir des données pour cette entité à partir de différentes ressources avec une interface différente, de l'API REST et json-rpc à graphQL et NQL. Ce qui permettra de maintenir la base et la structure croissantes du code dans la rigueur et l'ordre. Utilisation simple et intuitive. Fournir une description complète et précise de l'état des entités à tout moment. Nous voulons faire abstraction autant que possible des modules de nos utilisateurs de la couche de données.

Tout d'abord, nous avons examiné l'existant. Nous n’aimions rien. Pour une raison quelconque, toutes les bibliothèques pour travailler avec des données sont faites soit sur js, soit avec un tas de sorties. Ces tout gâcher tout. Ils ferment les yeux sur les développeurs, disant qu'une telle bibliothèque ne vous aidera pas beaucoup, que vous ne pourrez pas naviguer dans les types de vos données, vous ne pourrez pas exprimer leurs connexions. Les choses empirent encore lorsque plusieurs API de différents types sont utilisées ou qu'elles sont hétérogènes.

Toutes ces bibliothèques n'étaient pas suffisamment protégées par types. Par conséquent, ils créent plus de problèmes qu'ils n'en résolvent. Il est plus facile de ne pas les utiliser, mais de prendre des décisions spécifiques à votre domaine.

Par conséquent, au lieu de la bibliothèque bornée sur laquelle se trouvait la tâche, nous avons décidé d'en créer une bien plus puissante et abstraite - adaptée à tout. Et nous avons incroyablement cru en notre justesse, car ce n'est qu'ainsi que de très bonnes choses sont créées.



2


- (Antoha) Comme cela arrive souvent, j'attendais toutes sortes d'accès pour être autorisé à accéder au référentiel de l'entreprise. Et cela peut durer plusieurs semaines. Dans mon cas, il n'en a fallu qu'un. À ce moment, sur la base de mon expérience dans la création de bibliothèques similaires, j'ai décomposé la tâche, estimé la chronologie.

Le problème est qu'avant, comme tout le monde, je prenais des décisions très spécialisées. Le travail sur un outil universel a entraîné des problèmes supplémentaires. Le système de type est apparu extrêmement complexe, et ni moi ni personne d'autre n'avions d'expérience dans la conception de ce type. Pire encore, les développeurs autour de moi n'avaient aucune idée du typage statique.

Mais j'ai commencé à faire ce que je pensais avoir raison. Au Quotidien, je vous ai dit ce que je faisais et pourquoi, mais, en principe, personne ne me comprenait. Mes questions à l'équipe concernant le problème sont toujours restées sans réponse. C'était comme si je n'existais pas. Mec qui fait quelque chose de très compliqué et incompréhensible.

J'ai compris que javaScript ne fonctionnerait en aucune façon ici. J'avais besoin d'un PL avec un modèle de typage puissant, une excellente interaction avec javaScript, une grande communauté et un écosystème sérieux.

- (Phil) J'attends depuis longtemps qu'Antoha comprenne le charme de typeScript.

- (Antoha) Mais il y a un certain nombre de problèmes qui vous rendent fou. Il y a de la frappe, mais je n'ai toujours pas de correspondance parfaite entre l'exécution du programme et l'exécution par l'utilisateur. Le tapuscrit semble compliqué au premier abord. Il doit endurer. Vous voulez toujours prendre et jeter un objet ou un autre. Peu à peu, vous plongez dans la langue, dans son système de types, et ici une chose intéressante commence à se produire. Cela devient magique. Tout peut être tapé.

- (Phil) Pour la première fois de notre vie, nous nous sommes réunis et avons commencé quelque chose.

La première étape consiste pour l'utilisateur à décrire le schéma de données. On se demande à quoi ça ressemble. Quelque chose comme ça:

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

Source: https://habr.com/ru/post/fr454774/


All Articles