Fontes no GitHub

Como cheguei ao D
O principal motivo é que a postagem do blog original comparou linguagens digitadas estaticamente como Go e Rust, e fez referências respeitosas a Nim e Crystal, mas não mencionou D, que também se enquadra nesta categoria. Acho que isso tornará a comparação interessante.
Também gosto do D como linguagem e o mencionei em vários outros posts do blog.
Ambiente local
O manual contém informações abrangentes sobre como baixar e instalar o compilador de referência, DMD. Os usuários do Windows podem obter um instalador, enquanto os usuários do macOS podem usar o homebrew. No Ubuntu, acabei de adicionar o repositório apt e segui a instalação normal. Com isso, você obtém não apenas o DMD, mas também o dub, o gerenciador de pacotes.
Instalei o Rust para ter uma ideia de como seria fácil começar. Fiquei surpreso com o quão fácil é. Eu só precisei executar o instalador interativo , que cuidou do resto. Eu precisava adicionar ~ / .cargo / bin ao caminho. Você apenas teve que reiniciar o console para que as alterações tenham efeito.
Suporte de editores
Escrevi o Hashtrack no Vim sem muita dificuldade, mas provavelmente porque tenho alguma ideia do que está acontecendo na biblioteca padrão. Sempre tive a documentação aberta, porque às vezes usei um símbolo que não importei do pacote correto, ou chamei uma função com os argumentos errados. Observe que para a biblioteca padrão, você pode simplesmente escrever "import std;" e ter tudo à sua disposição. Para bibliotecas de terceiros, no entanto, você está por conta própria.
Eu estava curioso sobre o estado do kit de ferramentas, então pesquisei plug-ins para meu IDE favorito, Intellij IDEA. Eu encontrei issoe o instalou. Também instalei o DCD e o DScanner clonando seus respectivos repositórios e construindo-os e, em seguida, configurando o plugin IDEA para apontar para os caminhos corretos. Contate o autor desta postagem do blog para esclarecimentos.
Eu encontrei alguns problemas no início, mas eles foram corrigidos após a atualização do IDE e do plugin. Um problema que encontrei foi que ela não conseguia reconhecer meus próprios pacotes e ficava marcando-os como "possivelmente indefinidos". Mais tarde descobri que para que fossem reconhecidos, tive que colocar "module package_module_name;" na parte superior do arquivo.
Acho que ainda há um bug que .length não é reconhecido, pelo menos na minha máquina. Eu abri um problema no Github, você pode acompanhá-lo aquise você está curioso.
Se você estiver no Windows, ouvi coisas boas sobre o VisualD .
Gerenciamento de pacotes
Dub é o gerenciador de pacotes de fato em D. Ele baixa e instala dependências de code.dlang.org . Para este projeto, eu precisava de um cliente HTTP porque não queria usar cURL. Acabei com duas dependências, solicitações e sua dependência, cachetools, que não tem dependência própria. No entanto, por algum motivo, ele escolheu mais doze dependências:

acho que Dub as usa internamente, mas não tenho certeza disso.
Rust carregou muitos engradados ( Aprox: 228 ), mas isso provavelmente é porque a versão Rust tem mais recursos que a minha. Por exemplo, ele baixou rpassword , uma ferramenta que esconde caracteres de senha conforme ele os digita no terminal, semelhante à função getpass do Python.
Bibliotecas
Tendo pouco conhecimento do Graphql, eu não tinha ideia por onde começar. Uma pesquisa por "graphql" em code.dlang.org me levou à biblioteca correspondente, apropriadamente chamada de " graphqld ". No entanto, depois de estudá-lo, pareceu-me que se parecia mais com um plugin vibe.d do que com um cliente real, se houver.
Depois de examinar as solicitações de rede no Firefox, percebi que, para este projeto, posso simplesmente simular solicitações e transformações do graphql que irei enviar usando um cliente HTTP. As respostas são apenas objetos JSON que posso analisar usando as ferramentas fornecidas pelo pacote std.json. Com isso em mente, comecei a procurar clientes HTTP e resolvi as solicitações , que é um cliente HTTP fácil de usar, mas o mais importante, atingiu um certo nível de maturidade.
Copiei as solicitações de saída do analisador de rede e colei em arquivos .graphql separados, que importei e enviei com as variáveis apropriadas. A maior parte da funcionalidade foi colocada na estrutura GraphQLRequest porque eu queria inserir os vários endpoints e configurações conforme necessário para o projeto:
Fonte
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"
);
}
}
Aqui está um trecho de troca de pacotes. O código a seguir lida com a autenticação:
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();
}
}
Alerta de spoiler - nunca fiz isso antes.
Tudo acontece assim: a função main () cria uma estrutura Config a partir dos argumentos da linha de comando e a injeta na estrutura da Sessão, que implementa a funcionalidade dos comandos login, logout e status. O método createSession () constrói uma consulta graphQL lendo a consulta real do arquivo .graphql correspondente e passando as variáveis junto com ele. Eu não queria poluir meu código-fonte com mutações e consultas GraphQL, então movi-os para arquivos .graphql, que importei em tempo de compilação usando enum e import. O último requer um sinalizador do compilador para apontar para stringImportPaths (cujo padrão é view /).
Quanto ao método login (), sua única responsabilidade é enviar a solicitação HTTP e processar a resposta. Nesse caso, ele lida com erros potenciais, embora não com muito cuidado. Em seguida, ele armazena o token em um arquivo de configuração, que nada mais é do que um belo objeto JSON.
O método throwOnFailure não faz parte da funcionalidade principal da biblioteca de consulta. Na verdade, é uma função auxiliar que executa um tratamento de erros rápido e sujo:
void throwOnFailure(Response response)
{
if(!response.isSuccessful || "errors" in response.jsonBody)
{
string[] errors = response.errors;
throw new RequestException(errors.join("\n"));
}
}
Como D oferece suporte a UFCS , a sintaxe throwOnFailure (resposta) pode ser reescrita como response.throwOnFailure (). Isso facilita a incorporação em outras chamadas de método, como send (). Posso ter usado excessivamente essa funcionalidade ao longo do projeto.
Processamento de erro
D prefere exceções quando se trata de tratamento de erros. A justificativa é explicada em detalhes aqui . Uma das coisas que adoro é que erros não manipulados eventualmente aparecerão, a menos que sejam explicitamente conectados. É por isso que consegui evitar o tratamento simplificado de erros. Por exemplo, nestas linhas:
string token = response.jsonBody
["data"].object
["createSession"].object
["token"].str;
configuration.put("token", token);
Se o corpo da resposta não contiver um token ou qualquer um dos objetos que conduzam a ele, uma exceção será lançada, que irá borbulhar na função principal e, em seguida, explodir na frente do usuário. Se eu fosse usar o Go, teria que ser muito cuidadoso com os erros em cada etapa. E, francamente, como é chato escrever if err! = Null toda vez que a função é chamada, eu ficaria muito tentado a simplesmente ignorar o erro. No entanto, meu entendimento de Go é primitivo, e eu não ficaria surpreso se o compilador gritasse com você por não fazer nada com um retorno de erro, então sinta-se à vontade para me corrigir se eu estiver errado.
O tratamento de erros no estilo Rust, conforme explicado na postagem do blog original, foi interessante. Eu não acho que haja algo assim na biblioteca padrão D, mas tem havido discussões sobre como implementar isso como uma biblioteca de terceiros.
Websockets
Eu só quero destacar brevemente que não usei websockets para implementar o comando watch. Tentei usar o cliente websocket do Vibe.d, mas não funcionou com o back-end hashtrack porque fechava a conexão constantemente. No final, eu abandonei em favor do round robin, embora seja desaprovado. O cliente está funcionando desde que o testei com outro servidor da web, então posso voltar a isso no futuro.
Integração contínua
Para CI, configurei dois trabalhos de compilação: uma compilação de branch regular e uma versão mestre para garantir que compilações otimizadas de artefatos sejam baixadas.


Aproximadamente. As fotos mostram o tempo de montagem. Levando em consideração o carregamento de dependências. Reconstruir sem dependências ~ 4s
Consumo de memória
Usei o comando / usr / bin / time -v ./hashtrack --list para medir o uso de memória conforme explicado na postagem original do blog. Não sei se o uso de memória depende das hashtags que o usuário está seguindo, mas aqui estão os resultados de um programa em D compilado com dub build -b release:
Tamanho máximo do conjunto residente (kbytes): 10036
Tamanho máximo do conjunto residente (kbytes): 10164
Tamanho máximo do conjunto residente (kbytes): 9940
Tamanho máximo do conjunto residente (kbytes): 10060
Tamanho máximo do conjunto residente (kbytes): 10008
Não é ruim. Eu executei as versões Go e Rust com meu usuário hashtrack e obtive estes resultados:
Go construído com go build -ldflags "-s -w":
Tamanho máximo do conjunto residente (kbytes): 13684Ferrugem compilada com construção de carga - liberação:
Tamanho máximo do conjunto residente (kbytes): 13820
Tamanho máximo do conjunto residente (kbytes): 13904
Tamanho máximo do conjunto residente (kbytes): 13796
Tamanho máximo do conjunto residente (kbytes): 13600
Tamanho máximo do conjunto residente (kbytes): 9224Upd: O usuário do Reddit skocznymroczny recomendou testar os compiladores LDC e GDC também. Aqui estão os resultados:
Tamanho máximo do conjunto residente (kbytes): 9192
Tamanho máximo do conjunto residente (kbytes): 9384
Tamanho máximo do conjunto residente (kbytes): 9132
Tamanho máximo do conjunto residente (kbytes): 9168
LDC 1.22 compilado por dub build -b release --compiler = ldc2 (após adicionar saída de cor e getpass)
Tamanho máximo do conjunto residente (kbytes): 7816
Tamanho máximo do conjunto residente (kbytes): 7912
Tamanho máximo do conjunto residente (kbytes): 7804
Tamanho máximo do conjunto residente (kbytes): 7832
Tamanho máximo do conjunto residente (kbytes): 7804
D tem coleta de lixo, mas também suporta ponteiros inteligentes e, mais recentemente, uma metodologia experimental de gerenciamento de memória inspirada em Rust. Não tenho certeza de quão bem essas funções se integram com a biblioteca padrão, então decidi deixar o GC lidar com a memória para mim. Acho que os resultados são muito bons, considerando que não pensei sobre o consumo de memória enquanto escrevia o código.
Tamanho dos binários
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, ,
Acho que D é uma linguagem confiável para escrever ferramentas de linha de comando como esta. Não fui para dependências externas com muita frequência porque a biblioteca padrão continha a maior parte do que eu precisava. Coisas como análise de argumentos de linha de comando, manipulação de JSON, teste de unidade, envio de solicitações HTTP (com cURL ) estão disponíveis na biblioteca padrão. Se a biblioteca padrão não tiver o que você precisa, então existem pacotes de terceiros, mas acho que ainda há espaço para melhorias nesta área. Por outro lado, se sua mentalidade NIH não foi inventada aqui, ou se você deseja causar um impacto facilmente como um desenvolvedor de código aberto, então você definitivamente amará o ecossistema D.
Razões pelas quais eu usaria D
- sim