コードを読むコツ

コードを読むってすごく大変。他人の作ったコードならもっと大変。なのに仕様書が無かったりする。
「コードしか無いならそこから仕様書を作れ。今後はその仕様書を参考にしろ。」というのはわかるが、しょっちゅうコードが変更されるので、仕様書よりも先にコードが出来てしまっている。
これでは、仕様書を作ったとこで、すぐに手戻りが発生し、追いつかなくなる。
そこで、結局コードを読む力が必要になってしまう。(ほんとはダメなんだけどね。)

前置きが長くなったけど、コードを読むコツを自分なりにまとめてみた。

目的を明確にする

コードを読むにはまず、コードから何を調べたいのかを明確にすること。
目的が不明確なままコードを読むと何もよくわからないまま終わる事が多い。
つまり時間の無駄になるので、何を知りたいのか、調べたいのか、を明確にする。

基本は、入力、演算(制御)、出力

コードを読んでいて、たまに何をしてるのかわからなくなる時がある。
その時は、コンピュータの基本に戻ろう。
コンピュータの処理の流れの基本は、入力、演算(制御)、出力である。
なので、変数Aの流れを知りたい時は、
1. Aがいつ入力されているか
2. Aによって、どのように演算(制御)されているか
3. 2.によって、いつ、何が出力されるのか
を調べたら良い。
これは通信コマンドでも同じである。
本当に迷った時は、基本を思い出して、最初から読む。これは意外と大事。

先入観にとらわれない

複雑なコードを読んでいるときは、なんとなくこうかな? とか これは前にhogeという処理をしますと聞いたからそこまで調べなくてもいいや。 と思ってしまう事がある。
だが、どんな状況であれ、コードに書かれていることは絶対である。 コードに書かれている以上、それは常に正である。(仕様が間違っている可能性はあるけど)
あれ…?例外と正常の処理が一緒になってる?そんなわけない!おかしい!
と思っても、実はそれが正しかったりする。
思い込みはいけない。先入観にとらわれず、コードに書かれている。そこを重視する。
これはある程度の慣れが必要。


コードを読むのは大変だし、時間もかかる。
1オブジェクトによって、多々のオブジェクトに影響があったりすると、もう追いつけない。
そうなる前に仕様書を作ってくれよ と言いたいが、それができない以上、読めるようになるしかない。
でも、ほんとめんどくさいので、ソースコードには仕様書を作るという法律でも作ってくれ。

設計って何?

まず、先に言っておくと自分はど素人である。
仕事しているが、設計とかよくわかっていない。でも、設計って何なのかな?と思った場合、自分ならこう思うことをまとめてみた。

設計とは

設計というのは、自分がどういう考えを用いてプログラムを作るのかを、上位者およびプログラマに伝える方法である。
1人でプログラムを作るだけなら言語をいじればいい。別に設計とか必要ないと思う。自分で管理するには必要かもしれんが、最悪コードがある。

でも実際は1人で作ることは少ないだろう。チームで作るには他の人にどのようにプログラミングするか伝えないといけない。その為に必要なのが設計である。
なので、設計には言語が存在しない。他の人が見てもわかるように記述されている。

設計の仕方

設計の基本

設計の基本は、どういう考えを用いて作ったのかがわかるように記述する事。
プログラムの品質が悪くても良い。どういう考えを持って、こういう設計にしたのかを相手に伝えることが重要である。
見る側が評価できなければ、品質が良くても前には進む事もできない。
不具合があるかどうかもわからないので、見る側からしても先にすすめることはできない。
そんな時は、レビュー時に「この設計書何書いてるかわかりません。書き直して下さい。」と平気で言われる。コードが動いてても言われる。
だから、自分の考えがわかるような設計を絶対にすべき。

ベースがある場合の設計

仕事を引き受けた時、たいていはベースからコードをいじくり、新しいコードを追加したり、既存のコードを変更することが多いと思う。
そんな時は、設計する時に、ベースがどういう設計になっているのかを調査する。
ベースの設計が悪くても、その設計に基づいて行わないと顧客に「これだと、きちんとは動きますが、設計上は良く無いですね」と言われる。
なので、オブジェクト指向ならオブジェクト指向、手続きなら手続きで書いた方が良い。


なんか他にもいっぱいあった気がするけど、パッと思いついたのはこれくらい。
結局は、どういう考えを持って作ったのかが重要になってくる。
さて…考えなしに作ってしまったこのコードの設計書をどのようにして書こうか…
俺のデスマーチはまだ続きそうだ。

正規表現による置換の注意

問題

正規表現について、

hoge = 64;

の64のみ取り出したい場合の置換の正規表現として、

検索用語: .*(\d*).*
置換後: $1

と指定し、置換したが、64のみ取り出すことができない。

原因

初心者が陥りやすそうなミスだが

検索用語: .*(\d*).*

では、先頭の.*が「hoge = 64;」のすべてを含んでしまうので、(\d*)の対象が無いと言われてしまう。

解決策

検索用語: .*=\s*(\d+).*
置換後: $1

にすれば良い。
検索用語の意味は、
【なんでも文字列】【=】【スペースあってもよい】【数字列】【なんでも文字列】
という意味。

query-replace-regexpの置換する文字に改行を指定したい場合

とある日、query-replace-regexpで、「,」を「\n」に置換したくなった。

しかし、置換する文字に\nを指定しても、そのまま"\n"と置換されてしまう。

そこで、調べたところ、

Query replace regexp hoge with : C-qC-j

と入力すれば改行が置換される。

似たようなので、\t(タブ)に置換したい場合は、C-qC-iと入力すればタブに置換される。

cygwin設定

cygwinの設定ファイル

.bash_rc

# base-files version 3.9-3

# To up the latest recommended .bashrc content,
# look in /etc/defaults/etc/skel/.bashrc

# Modifying/skel/.bashrc directly will prevent
# setup from updating it.

# The copy in your home directory (~/.bashrc) is yours, please
# feel free to customise it to create a shell
# environment to your liking.  If you feel a change
# would be benificial to all, please feel free to send
# a patch to the cygwin mailing list.

# User dependent .bashrc file

# Environment Variables
# #####################

# TMP and TEMP are defined in the Windows environment.  Leaving
# them set to the default Windows temporary directory can have
# unexpected consequences.
unset TMP
unset TEMP

# Alternatively, set them to the Cygwin temporary directory
# or to any other tmp directory of your choice
# export TMP=/tmp
# export TEMP=/tmp

# Or use TMPDIR instead
# export TMPDIR=/tmp

# Shell Options
# #############

# See man bash for more options...

# Don't wait for job termination notification
# set -o notify

# Don't use ^D to exit
# set -o ignoreeof

# Use case-insensitive filename globbing
# shopt -s nocaseglob

# Make bash append rather than overwrite the history on disk
# shopt -s histappend

# When changing directory small typos can be ignored by bash
# for example, cd /vr/lgo/apaache would find /var/log/apache
# shopt -s cdspell


# Completion options
# ##################

# These completion tuning parameters change the default behavior of bash_completion:

# Define to access remotely checked-out files over passwordless ssh for CVS
# COMP_CVS_REMOTE=1

# Define to avoid stripping description in --option=description of './configure --help'
# COMP_CONFIGURE_HINTS=1

# Define to avoid flattening internal contents of tar files
# COMP_TAR_INTERNAL_PATHS=1

# If this shell is interactive, turn on programmable completion enhancements.
# Any completions you add in ~/.bash_completion are sourced last.
# case $- in
#   *i*) [[ -f /etc/bash_completion ]] && . /etc/bash_completion ;;
# esac


# History Options
# ###############

# Don't put duplicate lines in the history.
# export HISTCONTROL="ignoredups"

# Ignore some controlling instructions
# HISTIGNORE is a colon-delimited list of patterns which should be excluded.
# The '&' is a special pattern which suppresses duplicate entries.
# export HISTIGNORE=$'[ \t]*:&:[fb]g:exit'
# export HISTIGNORE=$'[ \t]*:&:[fb]g:exit:ls' # Ignore the ls command as well

# Whenever displaying the prompt, write the previous line to disk
# export PROMPT_COMMAND="history -a"


# Aliases
# #######

# Some example alias instructions
# If these are enabled they will be used instead of any instructions
# they may mask.  For example, alias rm='rm -i' will mask the rm
# application.  To override the alias instruction use a \ before, ie
# \rm will call the real rm not the alias.

# Interactive operation...
# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'

# Default to human readable figures
# alias df='df -h'
# alias du='du -h'

# Misc :)
# alias less='less -r'                          # raw control characters
# alias whence='type -a'                        # where, of a sort
# alias grep='grep --color'                     # show differences in colour

# Some shortcuts for different directory listings
# alias ls='ls -hF --color=tty'                 # classify files in colour
# alias dir='ls --color=auto --format=vertical'
# alias vdir='ls --color=auto --format=long'
# alias ll='ls -l'                              # long list
# alias la='ls -A'                              # all but . and ..
# alias l='ls -CF'                              #


# Functions
# #########

# Some example functions
# function settitle() { echo -ne "\e]2;$@\a\e]1;$@\a"; }

PATH=/bin/

.bash_profile

# base-files version 3.9-3

# To pick up the latest recommended .bash_profile content,
# look in /etc/defaults/etc/skel/.bash_profile

# Modifying /etc/skel/.bash_profile directly will prevent
# setup from updating it.

# The copy in your home directory (~/.bash_profile) is yours, please
# feel free to customise it to create a shell
# environment to your liking.  If you feel a change
# would be benifitial to all, please feel free to send
# a patch to the cygwin mailing list.

# ~/.bash_profile: executed by bash for login shells.

# source the system wide bashrc if it exists
if [ -e /etc/bash.bashrc ] ; then
  source /etc/bash.bashrc
fi

# source the users bashrc if it exists
if [ -e "${HOME}/.bashrc" ] ; then
  source "${HOME}/.bashrc"
fi

# Set PATH so it includes user's private bin if it exists
# if [ -d "${HOME}/bin" ] ; then
#   PATH=${HOME}/bin:${PATH}
# fi

# Set MANPATH so it includes users' private man if it exists
# if [ -d "${HOME}/man" ]; then
#   MANPATH=${HOME}/man:${MANPATH}
# fi

# Set INFOPATH so it includes users' private info if it exists
# if [ -d "${HOME}/info" ]; then
#   INFOPATH=${HOME}/info:${INFOPATH}
# fi

# 言語設定
export LANG=ja_JP.SJIS
export LESSCHARSET=japanese-sjis

alias explorer='C:/WINDOWS/explorer.exe'

.inputrc

# base-files version 3.9-3

# To pick up the latest recommended .inputrc content,
# look in /etc/defaults/etc/skel/.inputrc

# Modifying /etc/skel/.inputrc directly will prevent
# setup from updating it.

# The copy in your home directory (~/.inputrc) is yours, please
# feel free to customise it to create a shell
# environment to your liking.  If you feel a change
# would be benifitial to all, please feel free to send
# a patch to the cygwin mailing list.

# the following line is actually
# equivalent to "\C-?": delete-char
"\e[3~": delete-char

# VT
"\e[1~": beginning-of-line
"\e[4~": end-of-line

# kvt
"\e[H": beginning-of-line
"\e[F": end-of-line

# rxvt and konsole (i.e. the KDE-app...)
"\e[7~": beginning-of-line
"\e[8~": end-of-line

# VT220
"\eOH": beginning-of-line
"\eOF": end-of-line

# Allow 8-bit input/output
#set meta-flag on
#set convert-meta off
#set input-meta on
#set output-meta on
#$if Bash
  # Don't ring bell on completion
  #set bell-style none

  # or, don't beep at me - show me
  #set bell-style visible

  # Filename completion/expansion
  #set completion-ignore-case on
  #set show-all-if-ambiguous on

  # Expand homedir name
  #set expand-tilde on

  # Append "/" to all dirnames
  #set mark-directories on
  #set mark-symlinked-directories on

  # Match all files
  #set match-hidden-files on

  # 'Magic Space'
  # Insert a space character then performs
  # a history expansion in the line
  #Space: magic-space
#$endif

その他設定

Windows環境変数
PATH=C:\cygwin\bin\
HOME=C:\cygwin\home\UserName\
LANG=ja_JP.SJIS
にする。

grepとfind-tagのジャンプ先を読み取り専用にする

コードを読むことが多いので、grepとfind-tagのジャンプ先を読み取り専用にする。

;; 読み取り専用にする関数
(defun file-read-only-hook ()
  (toggle-read-only 1))

;; grepのジャンプ先を読み取り専用にする。
;; 正確には、next-error後に、読み取り専用にしている。
(add-hook 'next-error-hook 'file-read-only-hook)
;; find-tagのジャンプ先を読み取り専用にする。
(add-hook 'find-tag-hook 'file-read-only-hook)

grepのジャンプ先を読み取り専用にする処理はgrep以外も読み取り専用になる気がする…