首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >儿童银行账户

儿童银行账户
EN

Code Review用户
提问于 2018-05-24 17:06:20
回答 1查看 181关注 0票数 2

我在学克洛尔。不久前,我给孩子们开了一个python银行账户,教他们存钱。看看这里

Python是一个类,它创建了一个表,该表具有一个小的兴趣函数,它显示了如果它位于那里,他们的钱会做些什么。不用说,青少年甚至对15%的年利息也不感兴趣。他们现在就想要现金。

我现在正在Clojure中实现同样的功能。我还没加利息呢。因此,首先,我要求就如何做到这一点提出建议,第二件事将是对这种方法的任何批评。我的逻辑可能是错的,但功能确实起作用了。它的功能是:平衡、事务、报告(带有pprint表)。

名称空间:

代码语言:javascript
复制
(ns kids-bank.core
  (:require [clojure.string :as cstr])
  (:import java.util.Date)
  (:import java.io.File)   
  (:require [clojure-csv.core :as csv])
  (:require [java-time :as jt])
  (:require [clojure.pprint :as pp]))  


(def now jt/local-date)

从github复制代码片段,java.io似乎只适用于use,即使它是在ns中导入的。

代码语言:javascript
复制
(use 'clojure.java.io)

将帐户创建为csv文件.

代码语言:javascript
复制
(defn create-account
  "starts a new csv file using name of holder in the designated folder
  using clojure.java.io checks if file is there to prevent erasing 
existing file"
  [name]
  (let [file-name (str "path/to/folder" name "-account.csv")]
    (if (.exists (file file-name))
      (println "Account already exists")  
      (do
        (.createNewFile (file file-name))
        (with-open [wrtr (writer (file file-name))]
          (.write wrtr "date,amount,reference \n")) (println "That's done for you!"))))) 

创建帐户的

代码语言:javascript
复制
(do (create-account "noah")
    (create-account "robin")
    (create-account "bea"))

(def robin-account "path/to/robin-account.csv")
(def bea-account "/path/to/bea-account.csv")
(def noah-account "/path/to/noah-account.csv")

事务

代码语言:javascript
复制
(defn transaction ;TODO add an optional arg for date, default today.
  "accepts string for file name, amount, and reference as a string
  and adds the transaction to the account"
  [file n ref]
  (with-open [wrtr (writer file :append true)]
    (.write wrtr (str (local-date (now)) ", " n  ", " ref "\n"))))

解析CSV

我从@mo字节修改了一些解析csv函数,以便有一个默认的参数和带有或不带标题的解析选项。

代码语言:javascript
复制
(defn take-csv
  "takes file name and reads data.
  by default skips the first header
  change key :header to true in order to include it"
  [& {:keys [fname header] :or {header false}}]
  (if (= header false)
    (rest (with-open [file (reader fname)]
            (doall (map (comp first csv/parse-csv) (line-seq file)))))
    (with-open [file (reader fname)]
      (doall (map (comp first csv/parse-csv) (line-seq file))))))

平衡

我需要在map函数中使用来自Java的Double,并发现我需要定义一个Clojure函数,以便在map中使用fn参数,因此很少使用预代码(这就是实现它的方法吗?)

代码语言:javascript
复制
(defn dble [a]
  "takes a string returns a double"
  (Double. a))

以及:

代码语言:javascript
复制
(defn balance
  "accepts string for a file name returns the balance"
  [file]
  (reduce + (map dble (map second (take-csv :fname file :header false)))))

和最后的报告

以表的形式印在表格上:

代码语言:javascript
复制
(use 'clojure.pprint)

在这种情况下,use似乎也是必要的(如果有人有,请澄清一下)。

代码语言:javascript
复制
(defn pretty-report
  "returns a table of history of account"
  [fname]
  (let [header (first (take-csv :fname fname :header true))]
    (loop [body (take-csv :fname fname :header false) result []]
          (if (empty? body)
            (do (println (str fname)) (pp/print-table result) (println 
(str "the balance is: " (balance fname))))
        (recur
         (rest body)
         (conj result (zipmap (map keyword header) (first body))))))))

结果

一切似乎都正常,下面就是一个例子:

代码语言:javascript
复制
kids-bank.core> (create-account "test")
That's done for you!
kids-bank.core> (def test-account "path/to/test-account.csv")
#'kids-bank.core/test-account
kids-bank.core> (transaction test-account 10.0 "allowence")
nil
kids-bank.core> (transaction test-account 5.0 "repayment")
nil
kids-bank.core> (transaction test-account 20.0 "from grandad")
nil
kids-bank.core> (transaction test-account -13.0 "shopping")
nil
kids-bank.core> (balance test-account)
22.0
kids-bank.core> (pretty-report test-account)
/path/to/test-account.csv

|      :date | :amount |   :reference  |
|------------+---------+---------------|
| 2018-05-24 |    10.0 |     allowence |
| 2018-05-24 |     5.0 |     repayment |
| 2018-05-24 |    20.0 |  from grandad |
| 2018-05-24 |   -13.0 |      shopping |
the balance is: 22.0
nil
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-05-24 18:02:10

这不是坏代码。我所看到的最糟糕的事情是,您的许多代码都是通过副作用操作的。

您可以直接使用take-csv从文件中读取,也可以将副作用作为函数的目的,比如使用transactionpretty-report。你的所有功能都做得太多了,我会再把它们都分解一下。您必须在某个地方使用副作用,只需确保在可行的情况下显式地传递/返回数据,然后使用这些数据操作函数来帮助实现这些效果。通过这种方式,您可以测试数据的处理,例如,将数据写入磁盘。

我还要提出几点挑剔的观点:

你们的进口不必要地冗长。你可以把所有的:requires和:imports浓缩在一起。

代码语言:javascript
复制
(ns kids-bank.core
  (:require [clojure.string :as cstr]
            [clojure-csv.core :as csv]
            [java-time :as jt]
            [clojure.pprint :as pp])
  (:import [java.util.Date]
           [java.io.File]))
代码语言:javascript
复制
(doall (map (comp first csv/parse-csv) (line-seq file)))

可以说写得很奇怪。我只想在这里使用mapv来消除对doall的需求。如果您想要懒惰,请使用map,它将返回一个惰性序列。但是,如果您想严格计算所有内容,mapv将返回一个向量。

代码语言:javascript
复制
(mapv (comp first csv/parse-csv) (line-seq file))

对于(Double. a),我个人更喜欢

代码语言:javascript
复制
(Double/parseDouble a)

我觉得它更明确了一点。不过,它与parseDouble完全相同,因为构造函数只是将其委托给。

通常被认为是惯用的求和方法是

代码语言:javascript
复制
(apply + nums)

而不是使用reduce。这是因为+有一个过载,这是对参数的手动还原。不过,它们基本上是一样的。

不过,在同一领域,我认为您的双map可以使用comp和/或->>进行清理。

代码语言:javascript
复制
 (reduce + (map (comp dble second) (take-csv :fname file :header false)))

或者,我个人喜欢的

代码语言:javascript
复制
(->> (take-csv :fname file :header false)
     (map second)
     (map dble)
     (apply +))

这给了你一个很好的自上而下的阅读。

实际上,您不应该将多个loop累加器推到一行上。这就像在其他语言中在同一行中声明多个变量一样。这是一种通常被认为会损害可读性的实践,我认为这里肯定是这样的。变化

代码语言:javascript
复制
(loop [body (take-csv :fname fname :header false) result []]

代码语言:javascript
复制
(loop [body (take-csv :fname fname :header false)
       result []]

但是,在同样的部分中,您一直在firstrest上调用body。从一开始就解构body。我还将扩展您的打印调用,因为您再次尝试将过多的内容放在一行上,并切换到when以消除对do的需求。注意,xxs是很糟糕的名字,但我不知道该如何称呼它们。我不是在这里提倡一个字母的名字。

代码语言:javascript
复制
(defn pretty-report
  "returns a table of history of account"
  [fname]
  (let [header (first (take-csv :fname fname :header true))]
    (loop [[x & xs] (take-csv :fname fname :header false)
           result []]
      (when x
        (println (str fname))
        (pp/print-table result)
        (println (str "the balance is: " (balance fname)))
        (recur xs
          (conj result (zipmap (map keyword header) x)))))))

注意,(when x可以工作,因为当序列为空时,x默认为nil

不过,这整个部分只是对take-csv返回的内容的一个简化。它可以写成

代码语言:javascript
复制
(defn pretty-report
  "returns a table of history of account"
  [fname]
  (let [header (first (take-csv :fname fname :header true))]
    (reduce (fn [result x]
              (println (str fname))
              (pp/print-table result)
              (println (str "the balance is: " (balance fname)))

              (conj result (zipmap (map keyword header) x)))
            [] ; Starting result
            (take-csv :fname fname :header false))))

... :or {header false}}部分在take-csv中是不必要的。如果没有提供解构,header已经默认为nil,这就是falsey。

最好不要使用ref这个名字。这已经是一个核心功能了,并且由于阴影太差而导致错误。

代码语言:javascript
复制
(if (= header false) ...)

并不理想。我要么用

代码语言:javascript
复制
(if-not header ...)

或者只是颠倒身体的顺序然后用

代码语言:javascript
复制
(if header ...)
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/195103

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档