GitHub
视频上的来源
我如何来到D
主要原因是原始博客比较了Go和Rust之类的静态类型语言,并尊重地引用了Nim和Crystal,但没有提及D,D也属于此类。因此,我认为它将使比较有趣。
我也喜欢D作为一种语言,并在其他各种博客文章中都提到了D。
当地环境
该手册包含有关如何下载和安装参考编译器DMD的大量信息。Windows用户可以获取安装程序,而macOS用户可以使用自制软件。在Ubuntu上,我只是添加了apt信息库,然后按照常规安装进行安装。这样,您不仅可以获取DMD,还可以获取包管理器dub。
我安装了Rust,这样我就可以开始使用它了。我很惊讶这有多么容易。我只需要运行交互式安装程序即可,其余的工作都已完成。我需要在路径中添加〜/ .cargo / bin。您只需重新启动控制台即可使更改生效。
编辑的支持
我在Vim中编写Hashtrack并没有太大的困难,但这可能是因为我对标准库中发生的事情有一些了解。我总是打开文档,因为有时我使用的符号不是从正确的包中导入的,或者我用错误的参数调用了函数。请注意,对于标准库,您可以简单地编写“ import std;”。并准备好一切。但是,对于第三方库,您是一个人。
我对工具箱的状态感到好奇,所以我研究了我最喜欢的IDE Intellij IDEA的插件。我发现了这个并安装它。我还通过克隆它们各自的存储库并构建它们,然后配置IDEA插件以指向正确的路径来安装DCD和DScanner。请联系此博客文章的作者以进行澄清。
起初我遇到了一些问题,但在更新IDE和插件后已解决。我遇到的问题之一是她无法识别我自己的包裹,并一直将其标记为“可能未定义”。后来我发现,为了使它们被识别,我必须放入“ module package_module_name;”。在文件的顶部。
我认为至少在我的机器上仍然存在无法识别.length的错误。我在Github上开了一个问题,您可以在这里关注如果你很好奇。
如果您使用Windows,我听说过有关VisualD的好消息。
包装管理
Dub是D中的事实软件包管理器。它从code.dlang.org下载并安装依赖项。对于该项目,我需要一个HTTP客户端,因为我不想使用cURL。最后,我得到了两个依赖关系,即请求和它的依赖关系,cachetools,它们没有自己的依赖关系。但是,由于某种原因,他又选择了十二个依赖项:
我认为Dub在内部使用了它们,但是我不确定。
Rust装载了很多箱子(大约228个),但这可能是因为Rust版本具有比我更多的功能。例如,他下载了rpassword,这是一个在将密码字符输入终端时隐藏密码字符的工具,类似于Python的getpass函数。
图书馆
对graphql不太了解,我不知道从哪里开始。在code.dlang.org上搜索“ graphql”将我带到相应的库,恰当地命名为“ graphqld ”。但是,在研究它之后,在我看来,它看起来更像是vibe.d插件,而不是真正的客户端(如果有)。
在Firefox中检查了网络请求之后,我意识到对于这个项目,我可以简单地模拟将使用HTTP客户端发送的graphql请求和转换。响应只是我可以使用std.json包提供的工具解析的JSON对象。考虑到这一点,我开始寻找HTTP客户端并满足了request,这是一个易于使用的HTTP客户端,但更重要的是,它已经达到一定的成熟度。
我复制了来自网络分析器的传出请求,并将它们粘贴到单独的.graphql文件中,然后导入并与适当的变量一起发送。大多数功能已放入GraphQLRequest结构中,因为我想根据项目需要将各种端点和配置插入其中:
资源
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"
);
}
}
这是数据包交换代码段。以下代码处理身份验证:
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();
}
}
剧透警报-我以前从未这样做过。
一切都是这样的:main()函数从命令行参数创建一个Config结构,并将其注入到Session结构中,该结构实现了login,logout和status命令的功能。 createSession()方法通过从相应的.graphql文件读取实际查询并将变量与之一起传递来构造一个graphQL查询。我不想使用graphQL突变和查询来污染我的源代码,所以我将它们移到.graphql文件中,然后在编译时使用enum和import进行导入。后者需要一个编译器标志来指向stringImportPaths(默认为view /)。
至于login()方法,它的唯一职责是发送HTTP请求并处理响应。在这种情况下,它可以处理潜在的错误,尽管不是很小心。然后,它将令牌存储在配置文件中,实际上只不过是一个不错的JSON对象。
throwOnFailure方法不是查询库的核心功能的一部分。它实际上是一个辅助函数,可以执行一些快速而肮脏的错误处理:
void throwOnFailure(Response response)
{
if(!response.isSuccessful || "errors" in response.jsonBody)
{
string[] errors = response.errors;
throw new RequestException(errors.join("\n"));
}
}
由于D支持UFCS,因此throwOnFailure(响应)语法可以重写为response.throwOnFailure()。这样可以很容易地嵌入到其他方法调用中,例如send()。我可能在整个项目中都过度使用了此功能。
错误处理
D在处理错误时更喜欢使用异常。基本原理在这里详细解释。我喜欢的一件事是,除非明确堵塞,否则最终会弹出未处理的错误。这就是为什么我能够摆脱简化的错误处理。例如,在这些行中:
string token = response.jsonBody
["data"].object
["createSession"].object
["token"].str;
configuration.put("token", token);
如果响应主体不包含令牌或导致令牌的任何对象,则将引发异常,该异常将在主要功能中冒泡,然后在用户面前爆炸。如果要使用Go,则必须非常小心每个步骤的错误。而且,坦率地说,由于每次调用该函数时err!= Null都会令人烦恼,因此我很想忽略该错误。但是,我对Go的理解是原始的,如果编译器因没有返回错误而没有做任何事情而对您咆哮,我也不会感到惊讶,因此,如果我错了,请随时纠正我。
如原始博客文章中所述,Rust风格的错误处理很有趣。我认为D标准库中没有这样的东西,但是已经有关于将其实现为第三方库的讨论。
网络套接字
我只想简要指出一点,我没有使用websockets来实现watch命令。我尝试使用Vibe.d的websocket客户端,但是它无法与hashtrack后端一起使用,因为它一直关闭连接。最后,我放弃了它,转而使用循环法,即使它被皱眉了。自从我与另一个Web服务器进行测试以来,该客户端一直在工作,因此将来可能会再次使用它。
持续集成
对于CI,我设置了两个构建作业:常规分支构建和主发行版,以确保下载了优化的工件构建。
大约 图片显示了组装时间。考虑到依赖项的加载。重建无依赖〜4s
内存消耗
我使用了/ usr / bin / time -v ./hashtrack --list命令来测量内存使用情况,如原始博客文章中所述。我不知道内存使用情况是否取决于用户遵循的主题标签,但这是使用dub build -b release编译的D程序的结果:
最大常驻集大小(千字节):10036
最大常驻集大小(千字节):10164
最大常驻集大小(千字节):9940
最大常驻集大小(千字节):10060
最大常驻集大小(千字节):10008
不错。我使用hashtrack用户运行了Go和Rust版本,并得到了以下结果:
使用go build -ldflags“ -s -w”构建的Go:
最大常驻集大小(千字节):13684Rust编译为build --release:
最大常驻集大小(千
字节):13820
最大常驻集大小(千字节):13904
最大常驻集大小(千字节):13796最大常驻集大小(千字节):13600
最大居民集大小(千字节):9224更新:Reddit用户skocznymroczny建议也测试LDC和GDC编译器。结果如下:
最大居民集大小(千字节):9192
最大居民集大小(千字节):9384
最大居民集大小(千字节):9132
最大居民集大小(千字节):9168
由dub build -b release编译的LDC 1.22 --compiler = ldc2(添加颜色输出和getpass之后)
最大居民集大小(KB):7816
最大居民集大小(KB):7912
最大居民集大小(KB):7804
最大居民集大小(KB):7832
最大居民集大小(KB):7804
D具有垃圾回收功能,但它也支持智能指针,并且最近还支持受Rust启发的实验性内存管理方法。我不确定这些功能与标准库的集成程度如何,因此我决定让GC为我处理内存。考虑到我在编写代码时没有考虑内存消耗,我认为结果相当不错。
二进制大小
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, ,
我认为D是编写这样的命令行工具的可靠语言。我很少去外部依赖项,因为标准库包含了我所需的大部分内容。标准库中提供了诸如解析命令行参数,处理JSON,单元测试,发送HTTP请求(使用cURL)之类的功能。如果标准库缺少您所需的资源,则可以使用第三方软件包,但是我认为这方面仍有改进的余地。在另一方面,如果你的心态NIH这里没有发明,或者如果你想轻松地作为一个开源开发者产生影响,那么你一定会喜欢d生态系统。
原因我会用d
- 是