Open guevara opened 2 weeks ago
愚蠢的 on(‘data’, cb) 接口设计 | Jackarain 的 blog https://ift.tt/6PuqUaM Jack
愚蠢的 on('data', cb) 接口设计,在所有语言的 io 库中,但凡是类似这种设计的 API,都是十分愚蠢的设计。
on('data', cb)
正确的设计应该是类似 asio 中的 async_op(cb),其中 cb 只会被响应单次,除非在 cb 中重新发起 async_op(cb),当然也可发起其它异步操作。
async_op(cb)
因为 async_op(cb) 更为灵活,且包含了 on('data', cb) 这种用例。
另外由其当需要将这种异步方法转换成协程支持的时候,就能更加体现出它的优势所在了。
而不是在 on('data', cb) 中处理接收到的数据时,不停的判断接收到的数据是属于哪个流程,这既繁琐又易出错,这由其是在处理一些较复杂的应答式协议时更加糟糕无比,比如:
void onread(data) { // onread 就是 on('data', onread),接收到数据将被调用 if (state == login) { on_process_login(data); // 当前为login状态,则按login逻辑处理接收到的data return; } else if (state == getwork) { on_process_getwork(data); // 当前为getwork状态,则按getwork逻辑处理接收到的data return; } else if ... // 其它状态的判断... }
上面这种模式十分糟糕,除了需要用户维护复杂的 state 状态之外,其请求发送部分代码被迫割裂在其它地方运行,而接收响应部分被统一到了一个 onread 函数当中,这是十分糟糕的。
正确的打开方式,通常像这种很多这种应答式协议流程应该用协程来处理,将会十分优雅:
void start_work() { await login(request); await read(data); // 处理server返回的data...然后继续后面的工作 await getwork(request); await read(data); // 处理data,根据work完成业务 ... // 继续使用协程以最自然的同步流程方式处理后续流程,而不需要 state 保存状态... ... }
即便是那种简单的一问一答被动响应式的协议处理,在只处理请求的 server 端中的事件响应,也可以以下方式通过运行一个循环来异步读取就行了,而不必是 on('data', onread) :
on('data', onread)
void work_loop() { while (true) { data = await read(); if (data.type == request1) on_request1(data); if (data.type == request2) on_request2(data); ... } }
综上可看出,on('data', onread) 这类接口十分难以使用在这种复杂的应答协程,也很难转换成协程接口(转换成协程这个工作不适合在用户代码中去做库本该做的工作),其次状态机的维持极其容易出错。
async_op 这种类型的接口则很容易转换成协程,因为 IO发起 与 IO完成,能轻松一一对应到协程的 suspend 和 resume,即便不是协程也可亦或是 Resumable 函数,这也会得到一个较为清晰的业务流程。
async_op
suspend
resume
Resumable
简而言之,异步 io 库的 api 接口设计,不应该设计为 setInterval,而应是 setTimeout,但凡只设计为 setInterval 这种方式的 io 接口,都是十分糟糕的设计。
setInterval
setTimeout
当然,与其说是 setInterval 和 setTimeout 的区别,更应该说是事件驱动模型和异步操作模型的区别,一个库在实现IO接口时,应当尽可能设计能作为异步操作模型为基础的接口,或者直接设计为异步接口。
事件驱动模型在处理流程复杂的应答式协议时,需要维护状态机,使代码难以理解和维护;而异步操作模型则更加灵活,可以通过协程等方式将异步操作转换成同步代码,使代码结构更加清晰易懂。
愚蠢的 on(‘data’, cb) 接口设计 | Jackarain 的 blog
https://ift.tt/6PuqUaM
Jack
愚蠢的
on('data', cb)
接口设计,在所有语言的 io 库中,但凡是类似这种设计的 API,都是十分愚蠢的设计。正确的设计应该是类似 asio 中的
async_op(cb)
,其中 cb 只会被响应单次,除非在 cb 中重新发起async_op(cb)
,当然也可发起其它异步操作。因为
async_op(cb)
更为灵活,且包含了on('data', cb)
这种用例。另外由其当需要将这种异步方法转换成协程支持的时候,就能更加体现出它的优势所在了。
而不是在
on('data', cb)
中处理接收到的数据时,不停的判断接收到的数据是属于哪个流程,这既繁琐又易出错,这由其是在处理一些较复杂的应答式协议时更加糟糕无比,比如:上面这种模式十分糟糕,除了需要用户维护复杂的 state 状态之外,其请求发送部分代码被迫割裂在其它地方运行,而接收响应部分被统一到了一个 onread 函数当中,这是十分糟糕的。
正确的打开方式,通常像这种很多这种应答式协议流程应该用协程来处理,将会十分优雅:
即便是那种简单的一问一答被动响应式的协议处理,在只处理请求的 server 端中的事件响应,也可以以下方式通过运行一个循环来异步读取就行了,而不必是
on('data', onread)
:综上可看出,
on('data', onread)
这类接口十分难以使用在这种复杂的应答协程,也很难转换成协程接口(转换成协程这个工作不适合在用户代码中去做库本该做的工作),其次状态机的维持极其容易出错。async_op
这种类型的接口则很容易转换成协程,因为 IO发起 与 IO完成,能轻松一一对应到协程的suspend
和resume
,即便不是协程也可亦或是Resumable
函数,这也会得到一个较为清晰的业务流程。简而言之,异步 io 库的 api 接口设计,不应该设计为
setInterval
,而应是setTimeout
,但凡只设计为setInterval
这种方式的 io 接口,都是十分糟糕的设计。当然,与其说是
setInterval
和setTimeout
的区别,更应该说是事件驱动模型和异步操作模型的区别,一个库在实现IO接口时,应当尽可能设计能作为异步操作模型为基础的接口,或者直接设计为异步接口。事件驱动模型在处理流程复杂的应答式协议时,需要维护状态机,使代码难以理解和维护;而异步操作模型则更加灵活,可以通过协程等方式将异步操作转换成同步代码,使代码结构更加清晰易懂。
via Jackarain 的 blog https://ift.tt/mINOBFr
November 11, 2024 at 07:07PM