понедельник, 10 января 2011 г.

Процесс программирования с псевдокодом

Совершенный код рекламирует подход к написанию функций называемый "Процесс программирования с псевдокодом". Упрощенная суть его в том, что в начале описывается работа функции в виде обычного текста, а затем этот текст преобразуется в комментарии и под каждым из них записывается реальный код. Плюс данного подхода заключается в том, что значительно упрощается процесс написания сложной функции, и к тому же она сразу получается хорошо задокументированной. В процессе реального использования данного подхода я понял, что мне не хватает возможности по преобразованию моего описания функции в комментарий с сохранением отступов. В результате в моем конфигурационном файле emacs я написал следующий код:


(defun my-ppp-comment-region (top bottom)
  ""
  (interactive "r")
  (setq macro [home ?\M-m ?\C-z ?/ ?\C-z ?/ ? ])
;;  (apply-macro-to-region-lines top bottom macro
  (save-excursion
    (let ((end-marker (copy-marker bottom))
          next-line-marker)
      (goto-char top)
      (if (not (bolp))
          (forward-line 1))
      (setq next-line-marker (point-marker))
      (while (< next-line-marker end-marker)
        (goto-char next-line-marker)
        (save-excursion
          (forward-line 1)
          (set-marker next-line-marker (point)))
        (unless (looking-at "[ \t]*$")
            (save-excursion
              (let ((mark-active nil))
                (execute-kbd-macro (or macro last-kbd-macro)))))
        );;while
      (set-marker end-marker nil)
      (set-marker next-line-marker nil))))

и затем добавил
(define-key c-mode-base-map (kbd "C-M-;") 'my-ppp-comment-region)

в
c-mode-common-hook

например так:

(defun my-c-mode-common-hook ()
  ;; включить режимы auto-newline и hungry-delete
  (c-toggle-auto-hungry-state 1)
  (flyspell-prog-mode);;включаем проверку правописания в комментариях и строках 
  (ispell-change-dictionary "american")
  ;; работает как C-j  
  (define-key c-mode-base-map "\C-m" 'c-context-line-break)
  ;;добавлеям комментарии для Проектирования с Помощью Псевдокода
  (define-key c-mode-base-map (kbd "C-M-;") 'my-ppp-comment-region)
  ...
)
(add-hook 'c-mode-common-hook 'my-c-mode-common-hook)

в результате по C-M-; мое описание волшебным образом превращается в готовый комментарий к функции.

прагматизм

Недавно закончил читать "Программист-прагматик" Ханта и Томаса - книгу, которую многие рекомендовали прочитать.
Одним словом: понравилось. Книга написана простым и приятным языком. Освещен широкий круг вопросов от использования vcs, до способов определения требований проекта. Книга сильно перекликается с Совершенным кодом Макконелла и тот кто читал последнюю найдут в этой книге не очень много нового. Но повторить еще раз отличные советы никогда не бывает вредно, так о потраченном времени я нисколько не жалею. К тому же эта книга подтолкнула меня вступить в ACM с этого года - посмотрим сколько нового я успею узнать через эту организацию :).

улучшение compile-mode в emacs

В программировании важны мелочи. Хочется не тратить время на рутинные операции. Необходимо, чтобы инструмент которым ты пользуешься стал продолжением твоей руки - ее неотделимой частью и тогда многие операции будут делаться фактически автоматически. Пусть экономия может составлять пару секунд, но если это делается десятки раз в день, то может получиться солидный бонус.

Для меня таким инструментом, стал emacs. И операцией, которую мне всегда хотелось улучшить - была компиляция. Связка: "сохранить текст + скомпилировать/собрать и запустить тесты" выполняется мною в день очень часто и оптимизировать ее очень хотелось.

Чего хочется добиться:


  • автоматически сохранять все буфера перед началом компиляции: это избавит нас от надоедливых вопросах о необходимости сохранить измененные буфера
  • в некоторых каталогах проектов над которыми я работаю используются makefile из каталогов верхнего уровня: хочется, что бы при компиляции автоматически происходил поиск нежного файла - это избавит от необходимости переключаться на другой буфер перед компиляцией
  • хочется что бы после успешного завершения компиляции/тестирования окно compile автоматически закрывалось, а при ошибках оставалось бы для  дальнейших разбирательств
  • компиляцию и тестирование запускать различными комбинациями клавиш
Вооружившись следующими ссылками:

получилось следующее решение, удовлетворяющее вышеозначенным требованиям:

(defun upward-find-file (filename &optional startdir)
  "Move up directories until we find a certain filename. If we
  manage to find it, return the containing directory. Else if we
  get to the toplevel directory and still can't find it, return
  nil. Start at startdir or . if startdir not given"

  (let ((dirname (expand-file-name
 (if startdir startdir ".")))
(found nil) ; found is set as a flag to leave loop if we find it
(top nil))  ; top is set when we get
   ; to / so that we only check it once

    ; While we've neither been at the top last time nor have we found
    ; the file.
    (while (not (or found top))
      ; If we're at / set top flag.
      (if (string= (expand-file-name dirname) "/")
 (setq top t))
      
      ; Check for the file
      (if (file-exists-p (expand-file-name filename dirname))
 (setq found t)
; If not, move up a directory
(setq dirname (expand-file-name ".." dirname))))
    ; return statement
    (if found dirname nil)))


;; Helper for compilation. Close the compilation window if
;; there was no error at all.
(setq compilation-finish-functions 'compile-autoclose)
(defun compile-autoclose (buffer string)
  (cond ((and 
          (string-match "finished" string) 
          (not (string-match "*grep*" (buffer-name (get-buffer buffer)))))
         (bury-buffer "*compilation*")
         (winner-undo)
         (message "Build successful."))
        (t                                                                    
         (message "Compilation exited abnormally: %s" string))))

;;функция сохраняет содержимое всех буферов
;;и затем вызывает make из католога где находится текущий файл
(defun my-compile-file ()
  "Save all files and compile"
    (interactive)
    (save-some-buffers 1)
    (let ((default-directory (or (upward-find-file "Makefile") ".")))
      (compile (format "cd %s && make -k -j8" default-directory))
      )
  )

;; support for compile tests
(defun my-compile-tests-file ()
  "Save all files and compile"
    (interactive)
    (save-some-buffers 1)
    (let ((default-directory (or (upward-find-file "Makefile") ".")))
      (compile (format "cd %s && make -k -j8 check" default-directory))
      )
  )
;;устанавливаем нашу функцию
(global-set-key  [f7] 'my-compile-file)
(global-set-key  [(shift f7)] 'my-compile-tests-file)

Из недостатков:
если во время компиляции/тестирования переключать окна, то после успешного завершения процесса состояние окон может вернуться в другое состояние, что было перед началом процесса. В идеале было бы конечно создавать новое окно для компиляции, но тут надо разбираться дальше.