C.4.5 另一个缺陷 … 最为隐蔽

我刚才说的是 “基本可以打印(almost ready to print)”! 当然,print-graph 函数里还存在一个缺陷 … 它提供了 vertical-step 选项,却没有 horizontal-step 选项。top-of-range 刻度是以10为步长从10到300变化的,但 print-graph 函数目前只能按1为步长打印。

这是一个典型案例,常被称作最为隐蔽的一类缺陷,即遗漏型缺陷。这类缺陷无法通过阅读代码发现,因为问题并不在代码本身,而是缺失了某项功能。你最好的应对方式是尽早且频繁地测试程序;同时尽可能编写易于理解、易于修改的代码。无论何时都要记住,你写下的任何代码,迟早都会被重写,只是时间早晚而已。这是一条很难践行的准则。

需要修改的是 print-X-axis-numbered-line 函数,随后 print-X-axisprint-graph 函数也需要相应适配。工作量并不大,但有一处细节需要注意:数字应当与刻度标记对齐,这需要稍加思考。

下面是修正后的 print-X-axis-numbered-line

(defun print-X-axis-numbered-line
  (number-of-X-tics X-axis-leading-spaces
   &optional horizontal-step)
  "打印X轴刻度数字行"
  (let ((number X-axis-label-spacing)
        (horizontal-step (or horizontal-step 1)))
    (insert X-axis-leading-spaces)
    ;; 删除多余的前置空格。
    (delete-char
     (- (1-
         (length (number-to-string horizontal-step)))))
    (insert (concat
             (make-string
              ;; 插入空白字符。
              (-  (* symbol-width
                     X-axis-label-spacing)
                  (1-
                   (length
                    (number-to-string horizontal-step)))
                  2)
              ? )
             (number-to-string
              (* number horizontal-step))))
    ;; 插入剩余刻度数字。
    (setq number (+ number X-axis-label-spacing))
    (while (> number-of-X-tics 1)
      (insert (X-axis-element
               (* number horizontal-step)))
      (setq number (+ number X-axis-label-spacing))
      (setq number-of-X-tics (1- number-of-X-tics)))))

如果你正在 Info 中阅读本文,可以查看新版的 print-X-axisprint-graph 并对其求值。如果你阅读的是印刷版书籍,这里只展示修改过的代码行(完整内容篇幅过长)。

(defun print-X-axis (numbers-list horizontal-step)
  "打印与 NUMBERS-LIST 长度匹配的X轴刻度标签。
可选参数 HORIZONTAL-STEP 为正整数,用于指定
X轴每一列标签的递增步长。"
;; symbol-width 和 full-Y-label-width 的值
;; 由 print-graph 传入。
  (let* ((leading-spaces
          (make-string full-Y-label-width ? ))
       ;; symbol-width  graph-body-print 提供
       (tic-width (* symbol-width X-axis-label-spacing))
       (X-length (length numbers-list))
       (X-tic
        (concat
         (make-string
          ;; 生成空白字符串。
          (-  (* symbol-width X-axis-label-spacing)
              (length X-axis-tic-symbol))
          ? )
         ;; 将空白与刻度符号拼接。
         X-axis-tic-symbol))
       (tic-number
        (if (zerop (% X-length tic-width))
            (/ X-length tic-width)
          (1+ (/ X-length tic-width)))))

    (print-X-axis-tic-line
     tic-number leading-spaces X-tic)
    (insert "\n")
    (print-X-axis-numbered-line
     tic-number leading-spaces horizontal-step)))
(defun print-graph
  (numbers-list &optional vertical-step horizontal-step)
  "打印带标签的NUMBERS-LIST柱状图。
numbers-list为Y轴数值列表。  

可选参数 VERTICAL-STEP 为正整数,用于指定
Y轴每一行标签的递增步长。例如,步长5表示
每一行代表5个单位。

可选参数 HORIZONTAL-STEP 为正整数,用于指定
X轴每一列标签的递增步长。"
  (let* ((symbol-width (length graph-blank))
         ;; height 既是列表最大值,
         ;; 也是位数最多的数字。
         (height (apply 'max numbers-list))
         (height-of-top-line
          (if (zerop (% height Y-axis-label-spacing))
              height
            ;; 否则
            (* (1+ (/ height Y-axis-label-spacing))
               Y-axis-label-spacing)))
         (vertical-step (or vertical-step 1))
         (full-Y-label-width
          (length
           (concat
            (number-to-string
             (* height-of-top-line vertical-step))
            Y-axis-tic))))
    (print-Y-axis
     height-of-top-line full-Y-label-width vertical-step)
    (graph-body-print
        numbers-list height-of-top-line symbol-width)
    (print-X-axis numbers-list horizontal-step)))