我们来谈谈代码分解:上下文编程

当然,理想情况下,最好不要编写太多代码 。 如果您写的话,就如您所知,您需要认真思考 骨骼系统 系统架构与实现 肉类系统 系统逻辑。 在本说明中,我们将给出方便实现后者的方法。


我们将以Clojure语言为例,但是该原理本身也可以应用在其他函数式编程语言中(例如,我们在Erlang中使用的概念完全相同)。


主意


这个想法本身很简单,并基于以下陈述:


  • 任何逻辑总是由基本步骤组成;
  • 对于每个步骤,都需要某些数据,然后对其应用其逻辑并产生成功或失败的结果。

在伪代码级别,这可以表示如下:


do-something-elementary(context) -> [:ok updated_context] | [:error reason]


其中:


  • do-something-elementary -函数名称;
  • context函数参数,具有初始上下文的数据结构,函数从该上下文中获取所有必要的数据;
  • updated_context具有成功上下文的数据结构(如果成功),其中函数将其执行结果相加;
  • reason -数据结构,失败原因以及失败。

这就是整个想法。 然后-技术问题。 拥有1005亿个零件。


示例:用户进行购买


我们将在一个具体的简单示例上写下详细信息,该示例可在GitHub上找到
假设我们有很多有钱的用户,还有很多花钱的用户可以购买。 我们想编写一个代码来执行该批次的购买:


buy-lot(user_id, lot_id) -> [:ok updated_user] | [:error reason]


为简单起见,我们将在用户结构本身中存储金钱和大量用户。


为了实现,我们需要几个辅助功能。


until-first-error


在绝大多数情况下,业务逻辑可以表示为在发生错误之前需要采取的一系列步骤。 为此,我们将创建一个函数:


until-first-error(fs, init_context) -> [:ok updated_context] | [:error reason]


其中:


  • fs是一系列功能(基本动作);
  • init_context是初始上下文。

可以在GitHub上查看此功能的实现。


with-result-or-error


通常,基本操作是您只需要执行一些功能,如果成功,就将其结果添加到上下文中。 为此,我们有一个功能:


with-result-or-error(f, key, context) -> [:ok updated_context] | [:error reason]


通常,此功能的唯一目的是减小代码大小。


最后,我们的“美丽” ...


实现购买的功能


 1. (defn buy-lot [user_id lot_id] 2. (let [with-lot-fn (partial 3. util/with-result-or-error 4. #(lot-db/find-by-id lot_id) 5. :lot) 6. 7. buy-lot-fn (fn [{:keys [lot] :as ctx}] 8. (util/with-result-or-error 9. #(user-db/update-by-id! 10. user_id 11. (fn [user] 12. (let [wallet_v (get-in user [:wallet :value]) 13. price_v (get-in lot [:price :value])] 14. (if (>= wallet_v price_v) 15. (let [updated_user (-> user 16. (update-in [:wallet :value] 17. - 18. price_v) 19. (update-in [:lots] 20. conj 21. {:lot_id lot_id 22. :price price_v}))] 23. [:ok updated_user]) 24. [:error {:type :invalid_wallet_value 25. :details {:code :not_enough 26. :provided wallet_v 27. :required price_v}}])))) 28. :user 29. ctx)) 30. 31. fs [with-lot-fn 32. buy-lot-fn]] 33. 34. (match (util/until-first-error fs {}) 35. 36. [:ok {:user updated_user}] 37. [:ok updated_user] 38. 39. [:error reason] 40. [:error reason]))) 

让我们看一下代码:


  • 第34页: match是一个宏,用于匹配clojure.core.match库中模板的值;
  • p。34-40:我们将承诺的until-first-error函数应用于基本步骤fs ,从上下文中获取我们需要的数据并返回它,或者抛出错误;
  • 第2-5页:我们正在构建第一个基本操作(仅将当前上下文应用于该基本操作),该操作仅将key :lot数据添加到当前上下文;
  • 第7-29页:这里我们使用了熟悉的功能with-result-or-error ,但包装起来的操作却变得有些棘手:在一次交易中,我们检查用户是否有足够的钱,如果成功,我们进行购买(因为,默认情况下,我们的应用程序是多线程的 (最后一次在哪个地方看到单线程应用程序?) 我们必须为此做好准备)。

关于我们使用的其他功能的几句话:


  • lot-db/find-by-id(id) -通过id返回很多;
  • user-db/update-by-id!(user_id, update-user-fn) -将update-user-fn函数应用于用户user_id (在虚拟数据库中)。

并进行测试?...


从clojure REPL测试此样本应用程序 。 我们从项目根目录从控制台启动REPL:


 lein repl 

我们的财务用户是什么:


 context-aware-app.core=> (context-aware-app.user.db/enumerate) [:ok ({:id "1", :name "Vasya", :wallet {:value 100}, :lots []} {:id "2", :name "Petya", :wallet {:value 100}, :lots []})] 

我们有很多(货):


 context-aware-app.core=> (context-aware-app.lot.db/enumerate) [:ok ({:id "1", :name "Apple", :price {:value 10}} {:id "2", :name "Banana", :price {:value 20}} {:id "3", :name "Nuts", :price {:value 80}})] 

Vasya买一个苹果:


 context-aware-app.core=>(context-aware-app.processing/buy-lot "1" "1") [:ok {:id "1", :name "Vasya", :wallet {:value 90}, :lots [{:lot_id "1", :price 10}]}] 

还有香蕉:


 context-aware-app.core=> (context-aware-app.processing/buy-lot "1" "2") [:ok {:id "1", :name "Vasya", :wallet {:value 70}, :lots [{:lot_id "1", :price 10} {:lot_id "2", :price 20}]}] 

和“坚果”:


 context-aware-app.core=> (context-aware-app.processing/buy-lot "1" "3") [:error {:type :invalid_wallet_value, :details {:code :not_enough, :provided 70, :required 80}}] 

没有足够的钱买坚果。


合计


结果,使用上下文编程,将不再有大量的代码(不适合一个屏幕),也不再是“长方法”,“大类”和“长参数列表”。 它给出了:


  • 节省阅读和理解代码的时间;
  • 简化的代码测试
  • 重用代码的能力(包括使用复制粘贴+ dopilivanie文件);
  • 简化代码重构。

即 我们热爱和实践的一切。


Source: https://habr.com/ru/post/zh-CN413579/


All Articles