【测试】尝试用API客户端进行C语言代码的比较
代码
github:cotoha_api
目标
我想要比较C语言与其他语言。
标准
我想知道作为程序员来说,哪种编程语言更容易写。不过处理速度、执行文件大小和内存管理都可以不考虑。
目标
-
- golang
-
- rust
-
- nim
- vlang
Go语言
追溯其根源,C语言可谓是令和时代的代表性编程语言,此言不虚,确实粗犷简约。
言语规约过时了。这是个优点也是个缺点。我曾经是一名嵌入式工程师,长期以来一直使用汇编语言和C语言。即使对我这样的人来说,Go语言也很容易上手,能够迅速适应。
func (c *Cotoha) post(url string, header map[string]string, param map[string]string) (result []byte, err error) {
jsonData, err := json.Marshal(param)
if err != nil {
return nil, fmt.Errorf("post:%s", err.Error())
}
req, err := http.NewRequest(
http.MethodPost,
url,
bytes.NewBuffer(jsonData),
)
if err != nil {
return nil, fmt.Errorf("post:%s", err.Error())
}
for k, v := range header {
req.Header.Set(k, v)
}
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("post:%s", err.Error())
}
defer res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 201 {
return nil, fmt.Errorf("post:status is bad:%d", res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("post:%s", err.Error())
}
return body, nil
}
举个例子,上述代码是将golang的post处理函数化的结果。显而易见,错误处理异常地频繁。将post处理写成函数化的目的,也是因为错误处理导致代码行数过多。这种错误处理给我一种强烈的既视感。
这种语法与C语言完全相同。在C语言中,将状态码放在返回值中,并将指针作为参数来获取内容。而在golang中,将内容放在返回值1中,将状态码放在返回值2中。其他语言则可能采用了Result类型等各种不同的设计,但是golang在这方面既优点也缺点都没有改变。
尽管有人说「我讨厌 Go 语言」,但大多数人都在谈论它过于古老的语言约定。我能理解这种感受,但我认为这也是吸引那些无法适应新语言的工程师的一种因素。
富有病态般的lint非常结实。
Golang的lint非常严格。即使是细微的写法差异也绝对不会容忍。换行位置、空格数量和注释的排列都会一一检查。只需按下ctrl+s,它就会自动帮你格式化代码,并且对于注释更是会持续不断地发出波浪线直到你修正为止。
對於那些「討厭golang」的人來說,這也成為了批評的對象,認為它「缺乏自由」。我認為在開發過程中,通常會從過去的項目或示例中複製並粘貼,然後根據當前情況進行修改。然而,即使只是暫時寫的代碼,卻經常被要求無休止地修改語法,所以可以理解為什麼會感到焦慮不安。
总结:我确信开发现场大部分是些愚蠢的人的语言。
大多数的开发者都是笨蛋。特别是对于规模较大的项目,各种背景的工程师聚集在一起,结果只能变成一群笨蛋。这不仅仅是因为能力低下,而且相互之间很难调整各自的风格。像缩进的数量,if后面是否有空格,是否使用switch等等,这些细微的差异。这些差异累积起来会造成巨大的负担。而且往往人们不能灵活地改变规则等,所以变得愚蠢。
C语言可能是面临这个问题最多的编程语言。因此,我认为Go语言是为了克服这个问题而设计的。它采用了一种最基本的现代化风格,使得无论是哪个背景的人都能适应大型项目。它预见了编码规则等问题可能会变得名存实亡,通过lint工具来详细检查和指导。
很多工程师认为开发工作应该自由地有创造性地进行,这是其中一种理想。但我认为,世界上也存在着着火的金融案例,或者是大规模的泥淖案例,人们从不同的派遣单位纷纷聚集在一起,无论如何都要工作的现场。
必须逐步前进,就像爬行一样,而不是创造性地前进。这样的场景实际上存在,我认为Go语言正适合这种场景。虽然最好别有这样的事情,但如果有的话,我们应该心怀感激。
锈
由于内存管理而产生的特殊表示方法
在Rust中有一个叫做”所有权”的概念。
在C语言中,内存管理是由开发者手动进行的。开发者需要在使用之前分配内存,并将指针传递给函数等进行使用,然后在使用完毕后进行释放。特别是分配和释放必须成对出现,如果忘记释放,内存将一直被占用直到整个系统重新启动。
不必多言,因为这是危险的,所以引入了GC机制。尽管我没有详细了解,但总体上来说,它是一个将内存分配包装起来,调查引用次数等,并在不再需要时自动释放内存的机制。虽然有一些方法存在,但由于存在循环引用而无法被释放,或者由于需要确保安全性而推迟释放内存,因此在效率和速度方面存在一些问题。
在这里,提到了Rust的所有权概念。虽然我也没有完全理解。它的前提是在一个函数内分配的内存不能传递给另一个函数,这是一个规则。同时,在将指针传递给另一个函数时,可以通过”为该函数创建一个新指针,在返回值时释放”或者”传递所有权本身”的方式来管理内存,从而始终保持内存的清晰管理,这似乎是其功能。
为了熟练使用这个功能,需要相当程度的熟练度。变量的管理必须基于可变或固定的概念,并且必须始终知道这些变量属于哪个所有权。即使是要求严格的C语言也通常在实践中会在头部定义结构体,并使用自定义的构造函数和析构函数进行内存管理,这样在运营中只需修改结构体定义即可。严格的管理是如此困难。
不熟悉的语言规约
2021/01/30:因为是错误的知识,所以已删除。
总结:只适用于天才的语言。
我认为,Rust的最大特点在于上述的内存管理。然而,我不认为这种内存管理方法本身已经为所有人设计好了。多年来,许多人在对C语言不加思考地进行操作时导致了段错误。鉴于此,我们得到了一个教训,即开发了一种“即使是傻瓜也可以无需考虑地进行内存管理”的方法,也就是垃圾回收(GC)。我认为Go语言正是延续了这种思想。
然而,C语言并没有失败。如果正确编写,C语言可以实现最快的速度,而且在完善内存管理的情况下不会出现故障。从理论上来说没有任何问题。因此,我认为rust的想法是为了“通过增加规则来实现C语言中的最佳内存管理”。它几乎就像是对强大的C语言进行深入追求一样。编译器作为其助手保持坚固而高效。如果存在错误的写法,编译器会指出并提醒。
fn __access( &self ) -> Result<String, String> {
let res = ureq::post("https://api.ce-cotoha.com/v1/oauth/accesstokens")
.set("Content-Type", "application/json")
.send_json(
json!({
"grantType": "client_credentials",
"clientId": self.client_id,
"clientSecret": self.client_secret,
})
);
if ! res.ok() {
return Err(self.__concat("access:status is bad", res.status_line()));
}
let result = res.into_json();
if ! result.is_ok() {
return Err("access:Failed to parse response of cotoha api".to_string());
}
let json_data = result.ok().unwrap();
let mut access_token = json_data["access_token"].to_string();
if access_token == "" {
return Err("access:Authentication failed for cotoha api".to_string());
}
access_token.retain(|c| c != '"');
Ok(access_token)
}
用Rust编写与golang的post处理相同的代码,如下所示。与golang相比,非常精简。太精简了,不需要像golang那样将其封装为一个函数。虽然很巧妙,但通过查看这段代码,能立刻直观地理解它在做什么吗?
如果能够熟练运用的话,我认为它是一种理想的语言。如果项目是自家开发,并且能够清楚地了解工程师的素质,并且不必担心中途人员更替的问题,那么可能是实用的。然而,实际上,在拥有一定数量保持着良好素质的工程师的现场,并不像看起来那么简单,确实很少。
您好
柔软轻松的松散写法
proc post(url: string, header: openArray[tuple[key: string, val: string]], param: JsonNode): JsonNode =
var client = newHttpClient()
client.headers = newHttpHeaders(header)
var res: Response = client.request(
url = url,
httpMethod = HttpPost,
body = $param
)
if not res.status.contains("200") and not res.status.contains("201"):
return %*{ "err": fmt"post:status is bad:{res.status}" }
try:
return parseJson(res.body)
except:
return %*{ "err": fmt"post:parseJson is failed" }
首先写入代码的是Python。可以看出,它非常接近Python的语法。在Python中,变量需要进行类型声明。并且没有任何规约的限制。例如,Nim本来是不支持类(class)的,但是它有内置的宏(macro)功能,可以通过在此处定义来模拟类的存在。虽然Golang和Rust也没有类(class),但是Nim却允许使用这种自由,从编写的角度来说,这是非常方便的。(虽然Golang和Rust也可以写类似的结构,但相比于Nim的伪类,我认为它们过于受限制)。
细致入微的规格
您是否有try语句是一回事,而不是一个产品是优秀还是有缺陷。当我们看到类似golang的地狱般的错误处理时,才会真切地感受到有一个能够轻松处理错误的语言是多么令人欣慰。关键不在于try语句是否存在,而是要感激它能够轻松地引入方便级别的规范。
Golang和Rust都有明确的思想存在,它们在方便与否的程度上不会轻易地为我们提供功能,这样的形象也存在。而Nim则希望能够以无压力为约定,允许我们非常轻松地编写代码。
总结:这是一种自由度很高的语言,允许人们表达各种事物。
Nim能够轻松地通过宏来改变写作风格,并且还可以轻松地添加方便的功能。它能够容忍像try这样懒散的错误处理方式,是一种非常宠爱工程师的语言。对于想要从事编译语言开发的爱好者来说,它可能是最理想的语言。
然而,若果將其應用於工作中,就會遲疑不決。因為它的自由度太高了,很可能允許個人隨心所欲地編寫代碼。Golang 有病態的性格,強制每個人都使用相同的東西,而 Rust 則有優秀的編譯器只防止錯誤。Nim 沒有這種嚴格性,因此需要強大的自我管理能力。
把以下内容用中文进行本地化解释,请提供一个选项:
编程语言
需要根据未经整理的文档,直接查看源代码进行实施。
虽然有些地方无法避免因为这门新语言而导致的问题,但是文档功能失效。文档中只有示例代码,没有类似于所谓的doc的内容。即使是这些示例代码似乎也过时了,即便复制粘贴也无法运行。我们需要把文档视作只为了了解搜索关键词的工具。
庆幸的是,源代码:vlib已经得到维护,并且易于阅读。虽然这似乎是理所当然的,但却并非理所当然。以前为了确认PHP的实现而阅读源代码的时候,发现了太多的独特定义,只盗窃阅读最必要的部分也是相当困难的。
处理错误很简单。
Vlang似乎是受到golang的影响而产生的编程语言。因此,其语法本身与golang非常相似,但错误处理方式更加简易。请您详细阅读官方文档中的/Result类型和错误处理部分以了解更多信息。
resp := http.get(url) or {
panic(err)
}
println(resp.body)
基本上是这个形式。可以附加或来描述错误处理。
resp := http.get(url) or {
return error(err)
}
println(resp.text)
如果想要有更好的处理方式,可以这样做。
fn http_get() ?resp {
resp := http.get(url) or {
return error(err)
}
println(resp.text)
}
如果在返回值上加上?,即使不刻意写return,它也会直接返回成功/失败。
resp := http.get(url)?
println(resp.body)
如果不需要那种细致的控制,只要是方便的工具,这个就可以了。听说它会执行与顶部的代码(panic)相同的操作。我认为改进了导致对Golang产生厌恶的错误处理是个好消息。
似乎还有一些规格错误的问题未解决。
由于json编码没有与字典数组连接,所以易用性较差。虽然这可能是未来改进的部分,但无论如何都还不错。
例如,vlang将单引号视为优先级较高的表示法。因此,当传递双引号字符串时,会自动将其转换为单引号。
mut data := {
"s1": s1,
"s2": s2,
}
if senType != "" {
data["type"] = senType
}
if dicType != "" {
data["dic_type"] = dicType
}
ret := json.encode(data.str())
上述的是将map先转换成string,然后进行json编码。
"{'s1':'s1', 's2':'s2'}"
结果如下:s1和s2作为主键被放在单引号中。
resp := http.fetch(
"https://api.ce-cotoha.com/api/dev/nlp/v1/" + target,
{
method: "POST",
data: data,
headers: {
"Content-Type": "application/json;charset=UTF-8",
"Authorization": "Bearer " + access_token,
}
}
)?
当将这个作为数据发送时,vlang会将双引号转换为单引号。
data: '{'s1':'s1', 's2':'s2'}'
结果,由于所有引号变成了单引号,导致字符串变得无法分辨起止。
fn (c Cotoha) json_encode(m map[string]string) string {
mut ret := "{"
for k, v in m {
ret += '"$k":"$v",'
}
ret = ret.trim_right(",")
ret += "}"
return ret
}
为了避免这个问题,我准备了一个自己的函数。虽然现在可以正常工作,但却是意想不到的陷阱。
问题是,首先是关于“统一使用单引号”的理念,以及随之而来的“自动将双引号转换为单引号”的要求是否必要。这一理念与golang强制错误处理不同,与rust施加复杂的内存管理规则也不同,因为它并没有对效率和优化做出贡献。然而,与nim提高便利性的情况不同,事实上只是降低了自由度的结果。
我的意思是,由于加入了没有必要的规范,导致了不必要的错误。我个人认为这不仅仅是一个发展中的问题,也可能是开发语言的粗心和浮躁。
总结:方向不明的golang变种
目前,相较于Golang,肯定存在更高的便利性。虽然我认为这点是值得赞赏的,但如果要继承Golang的思想,是否应该提供多个错误处理的选项而不只是or或?之一呢?我认为应该只实现其中一个选项,而应该尽量消除不必要的选择。这是因为避免由工程师导致的书写风格不统一恰恰是Golang故意追求的简约粗犷之道。就引号的观念而言,感觉统一会更方便,这种直觉一直无法抹去。
不过,在1.0版本尚未推出的现状下,很难说什么。虽然有期望,但感觉不大乐观。