Sources sur GitHub
Video au clic
Comment je suis arrivé à D
La raison principale est que l'article de blog original comparait des langages statiquement typés comme Go et Rust, et faisait des références respectueuses à Nim et Crystal, mais ne mentionnait pas D, qui entre également dans cette catégorie. Je pense donc que cela rendra la comparaison intéressante.
J'aime aussi D en tant que langue et je l'ai mentionné dans divers autres articles de blog.
Environnement local
Le manuel contient des informations détaillées sur la façon de télécharger et d'installer le compilateur de référence, DMD. Les utilisateurs de Windows peuvent obtenir le programme d'installation, tandis que les utilisateurs de macOS peuvent utiliser homebrew. Sur Ubuntu, je viens d'ajouter le référentiel apt et j'ai suivi l'installation normale. Avec cela, vous obtenez non seulement le DMD, mais aussi le dub, le gestionnaire de paquets.
J'ai installé Rust pour avoir une idée de la facilité de démarrage. J'ai été surpris de voir à quel point c'est facile. Je n'avais besoin que de lancer le programme d' installation interactif , qui s'occupait du reste. J'avais besoin d'ajouter ~ / .cargo / bin au chemin. Il vous suffit de redémarrer la console pour que les modifications prennent effet.
Support par les éditeurs
J'ai écrit le Hashtrack dans Vim sans trop de difficulté, mais c'est probablement parce que j'ai une idée de ce qui se passe dans la bibliothèque standard. J'ai toujours eu la documentation ouverte, car parfois j'utilisais un symbole que je n'importais pas du bon package, ou j'appelais une fonction avec les mauvais arguments. Notez que pour la bibliothèque standard, vous pouvez simplement écrire "import std;" et ayez tout à votre disposition. Pour les bibliothèques tierces, cependant, vous êtes seul.
J'étais curieux de connaître l'état de la boîte à outils, alors j'ai cherché des plugins pour mon IDE préféré, Intellij IDEA. J'ai trouvé çaet l'a installé. J'ai également installé DCD et DScanner en clonant leurs dépôts respectifs et en les construisant, puis en configurant le plugin IDEA pour qu'il pointe vers les chemins corrects. Contactez l'auteur de ce billet de blog pour plus de précisions.
J'ai rencontré quelques problèmes au début, mais ils ont été corrigés après la mise à jour de l'EDI et du plugin. L'un des problèmes que j'ai rencontrés était qu'elle ne pouvait pas reconnaître mes propres paquets et les marquait comme "peut-être indéfini". Plus tard, j'ai découvert que pour qu'ils soient reconnus, je devais mettre "module package_module_name;" en haut du fichier.
Je pense qu'il y a encore un bogue que .length n'est pas reconnu, du moins sur ma machine. J'ai ouvert un numéro sur Github, vous pouvez le suivre icisi vous êtes curieux.
Si vous êtes sous Windows, j'ai entendu de bonnes choses à propos de VisualD .
Gestion des packages
Dub est le gestionnaire de paquets de facto dans D. Il télécharge et installe les dépendances à partir de code.dlang.org . Pour ce projet, j'avais besoin d'un client HTTP car je ne voulais pas utiliser cURL. Je me suis retrouvé avec deux dépendances, les requêtes et sa dépendance, cachetools, qui n'a pas de dépendance propre. Cependant, pour une raison quelconque, il a choisi douze autres dépendances:
je pense que Dub les utilise en interne, mais je n'en suis pas sûr.
Rust a chargé beaucoup de caisses ( environ 228 ), mais c'est probablement parce que la version Rust a plus de fonctionnalités que la mienne. Par exemple, il a téléchargé rpassword , un outil qui masque les caractères du mot de passe au fur et à mesure qu'il les saisit dans le terminal, similaire à la fonction getpass de Python.
Bibliothèques
Ayant peu de compréhension de graphql, je ne savais pas par où commencer. Une recherche de "graphql" sur code.dlang.org m'a conduit à la bibliothèque correspondante, bien nommée " graphqld ". Cependant, après l'avoir étudié, il m'a semblé qu'il ressemblait plus à un plugin vibe.d qu'à un vrai client, le cas échéant.
Après avoir examiné les requêtes réseau dans Firefox, j'ai réalisé que pour ce projet, je peux simplement simuler des requêtes graphql et des transformations que j'enverrai à l'aide d'un client HTTP. Les réponses ne sont que des objets JSON que je peux analyser à l'aide des outils fournis par le package std.json. Dans cet esprit, j'ai commencé à chercher des clients HTTP et je me suis installé sur les requêtes , qui est un client HTTP facile à utiliser, mais surtout, qui a atteint un certain niveau de maturité.
J'ai copié les demandes sortantes de l'analyseur de réseau et les ai collées dans des fichiers .graphql séparés, que j'ai ensuite importés et envoyés avec les variables appropriées. La plupart des fonctionnalités ont été placées dans la structure GraphQLRequest car je voulais y insérer divers points de terminaison et configurations selon les besoins du projet:
La source
struct GraphQLRequest
{
string operationName;
string query;
JSONValue variables;
Config configuration;
JSONValue toJson()
{
return JSONValue([
"operationName": JSONValue(operationName),
"variables": variables,
"query": JSONValue(query),
]);
}
string toString()
{
return toJson().toPrettyString();
}
Response send()
{
auto request = Request();
request.addHeaders(["Authorization": configuration.get("token", "")]);
return request.post(
configuration.get("endpoint"),
toString(),
"application/json"
);
}
}
Voici un extrait d'échange de paquets. Le code suivant gère l'authentification:
struct Session
{
Config configuration;
void login(string username, string password)
{
auto request = createSession(username, password);
auto response = request.send();
response.throwOnFailure();
string token = response.jsonBody
["data"].object
["createSession"].object
["token"].str;
configuration.put("token", token);
}
GraphQLRequest createSession(string username, string password)
{
enum query = import("createSession.graphql").lineSplitter().join("\n");
auto variables = SessionPayload(username, password).toJson();
return GraphQLRequest("createSession", query, variables, configuration);
}
}
struct SessionPayload
{
string email;
string password;
//todo : make this a template mixin or something
JSONValue toJson()
{
return JSONValue([
"email": JSONValue(email),
"password": JSONValue(password)
]);
}
string toString()
{
return toJson().toPrettyString();
}
}
Alerte spoiler - Je n'ai jamais fait cela auparavant.
Tout se passe comme ceci: la fonction main () crée une structure Config à partir des arguments de la ligne de commande et l'injecte dans la structure Session, qui implémente la fonctionnalité des commandes login, logout et status. La méthode createSession () construit une requête graphQL en lisant la requête réelle à partir du fichier .graphql correspondant et en lui passant les variables. Je ne voulais pas polluer mon code source avec des mutations et des requêtes graphQL, alors je les ai déplacés dans des fichiers .graphql, que j'importe ensuite au moment de la compilation en utilisant enum et import. Ce dernier nécessite un indicateur de compilateur pour pointer vers stringImportPaths (qui par défaut est view /).
Quant à la méthode login (), sa seule responsabilité est d'envoyer la requête HTTP et de traiter la réponse. Dans ce cas, il gère les erreurs potentielles, mais pas très soigneusement. Il stocke ensuite le jeton dans un fichier de configuration, qui n'est en réalité rien de plus qu'un bel objet JSON.
La méthode throwOnFailure ne fait pas partie des fonctionnalités principales de la bibliothèque de requêtes. Il s'agit en fait d'une fonction d'assistance qui gère rapidement les erreurs:
void throwOnFailure(Response response)
{
if(!response.isSuccessful || "errors" in response.jsonBody)
{
string[] errors = response.errors;
throw new RequestException(errors.join("\n"));
}
}
Puisque D prend en charge UFCS , la syntaxe throwOnFailure (réponse) peut être réécrite sous la forme response.throwOnFailure (). Cela facilite l'intégration dans d'autres appels de méthode tels que send (). J'ai peut-être abusé de cette fonctionnalité tout au long du projet.
Traitement des erreurs
D préfère les exceptions en matière de gestion des erreurs. La justification est expliquée en détail ici . Une des choses que j'aime, c'est que les erreurs non gérées finiront par apparaître à moins qu'elles ne soient explicitement branchées. C'est pourquoi j'ai pu m'éloigner de la gestion simplifiée des erreurs. Par exemple, dans ces lignes:
string token = response.jsonBody
["data"].object
["createSession"].object
["token"].str;
configuration.put("token", token);
Si le corps de la réponse ne contient pas de jeton ou l'un des objets qui y mènent, une exception sera lancée, qui bouillonnera dans la fonction principale puis explosera devant l'utilisateur. Si je devais utiliser Go, je devrais être très prudent avec les erreurs à chaque étape. Et, franchement, comme il est ennuyeux d'écrire if err! = Null à chaque fois que la fonction est appelée, je serais très tenté d'ignorer simplement l'erreur. Cependant, ma compréhension de Go est primitive, et je ne serais pas surpris si le compilateur vous aboie pour ne rien faire avec un retour d'erreur, alors n'hésitez pas à me corriger si je me trompe.
La gestion des erreurs de style rouille, comme expliqué dans l'article de blog original, était intéressante. Je ne pense pas qu'il y ait quelque chose de semblable dans la bibliothèque standard D, mais il y a eu des discussions sur l'implémentation de cela en tant que bibliothèque tierce.
Websockets
Je veux juste souligner brièvement que je n'ai pas utilisé de websockets pour implémenter la commande watch. J'ai essayé d'utiliser le client websocket de Vibe.d mais cela ne pouvait pas fonctionner avec le backend de hashtrack car il n'arrêtait pas de fermer la connexion. En fin de compte, je l'ai abandonné en faveur du tournoi à la ronde, même s'il est mal vu. Le client fonctionne depuis que je l'ai testé avec un autre serveur Web, donc je pourrais y revenir dans le futur.
Intégration continue
Pour CI, j'ai configuré deux tâches de construction: une construction de branche régulière et une version principale pour m'assurer que les versions optimisées des artefacts sont téléchargées.
Environ. Les photos montrent le temps de montage. Prise en compte du chargement des dépendances. Reconstruire sans dépendances ~ 4s
Consommation de mémoire
J'ai utilisé la commande / usr / bin / time -v ./hashtrack --list pour mesurer l'utilisation de la mémoire comme expliqué dans le billet de blog original. Je ne sais pas si l'utilisation de la mémoire dépend des hashtags que l'utilisateur suit, mais voici les résultats d'un programme D compilé avec dub build -b release:
Taille maximale du jeu résident (Ko): 10036
Taille maximale du jeu résident (Ko): 10164
Taille maximale du jeu résident (Ko): 9940
Taille maximale du jeu résident (Ko): 10060
Taille maximale du jeu résident (Ko): 10008
Pas mal. J'ai exécuté les versions Go et Rust avec mon utilisateur de hashtrack et j'ai obtenu ces résultats:
Go construit avec go build -ldflags "-s -w":
Taille maximale du jeu de résidents (Ko): 13684Rust compilé avec la construction de la cargaison - libération:
Taille maximale du jeu de résidents (Ko): 13820
Taille maximale du jeu de résidents (Ko): 13904
Taille maximale du jeu de résidents (Ko): 13796
Taille maximale du jeu de résidents (Ko): 13600
Taille maximale du jeu résident (Ko): 9224Mise à jour: l' utilisateur de Reddit skocznymroczny a recommandé de tester également les compilateurs LDC et GDC. Voici les résultats:
Taille maximale du jeu résident (Ko): 9192
Taille maximale du jeu résident (Ko): 9384
Taille maximale du jeu résident (Ko): 9132
Taille maximale du jeu résident (Ko): 9168
LDC 1.22 compilé par dub build -b release --compiler = ldc2 (après l'ajout de la sortie couleur et getpass)
Taille maximale du jeu résident (Ko): 7816
Taille maximale du jeu résident (Ko): 7912
Taille maximale du jeu résident (Ko): 7804
Taille maximale du jeu résident (Ko): 7832
Taille maximale du jeu résident (Ko): 7804
D a garbage collection, mais il prend également en charge les pointeurs intelligents et, plus récemment, une méthodologie expérimentale de gestion de la mémoire inspirée de Rust. Je ne suis pas tout à fait sûr de savoir si ces fonctions s'intègrent bien à la bibliothèque standard, j'ai donc décidé de laisser le GC gérer la mémoire à ma place. Je pense que les résultats sont plutôt bons étant donné que je n'avais pas pensé à la consommation de mémoire lors de l'écriture du code.
Taille des binaires
Rust, cargo build --release: 7.0M
D, dub build -b release: 5.7M
D, dub build -b release --compiler=ldc2: 2.4M
Go, go build: 7.1M
Go, go build -ldflags "-s -w": 5.0M
.. — , , . Windows dub build -b release 2 x64 ( 1.5M x86-mscoff) , Rust Ubuntu18 - openssl, ,
Je pense que D est un langage fiable pour écrire des outils de ligne de commande comme celui-ci. Je ne suis pas allé très souvent aux dépendances externes car la bibliothèque standard contenait la plupart de ce dont j'avais besoin. Des éléments comme l'analyse des arguments de ligne de commande, le traitement JSON, les tests unitaires, l'envoi de requêtes HTTP (avec cURL ) sont tous disponibles dans la bibliothèque standard. Si la bibliothèque standard ne dispose pas de ce dont vous avez besoin, des packages tiers existent, mais je pense qu'il y a encore place à amélioration dans ce domaine. D'un autre côté, si votre mentalité NIH n'est pas inventée ici, ou si vous voulez facilement avoir un impact en tant que développeur open source, alors vous allez certainement adorer l'écosystème D.
Raisons pour lesquelles j'utiliserais D
- Oui