上川的ソフトウェア開発メソッド講座

はじめに

この資料は,「上川的ソフトウェア開発メソッド講座」 (2002年10月18日,同志社大学)において利用するための資料である. フリーソフトウェアの開発,またグループでソフトウェアを開発 するときに,上川と一緒に開発する人はこういうことを してほしい,という希望を伝えるために行う講座だ. また,フリーソフトウェアの世界で行われている 開発の手法とツールの利用方法についての解説を行う.

この講座の前にはautomakeゼミがあったので,そこで automakeの基本的な使い方は分かっているものだとする.

問題提起

共同作業においての開発

メーリングリスト(以下ML)などでの共同体がある. ソースをCVS等で共有する. 研究班とか,開発グループとか,友人とか, とりあえずどういう関係なのかは分からないが, 共同で作業することになることは結構ある. また,研究が後輩に受け継がれる事などがあり, 後輩に伝承する必要があったりする.

共同作業での作業

共同作業でのプログラム書きは以下の行為が大半だ:

まず他人の書いたプログラムをコンパイルできるようになるのが 最初で,それを実行できるようになり,バグを取り除くのが次, そして自分が利用したい機能を追加するための 機能拡張がついてくる. 共同作業を行うためには,極論すればこのステップが簡単であるのが よいプログラムである.

コードがソースからコンパイルできることは重要である. 自分のシステムではコンパイルできるが, CVSから新規にチェックアウトしたソースを コンパイルしようとしてもなかなかコンパイルできないと いうことになると,その後継者は何の作業をするまえに, まずコンパイルが通るように作業をする必要がある.

このためには,説明書などの存在が重要になる. 説明書は書くのに労力が必要となり,また, 新しく書き直しつづけるプログラムにたいして 変更維持するのは大変である. そこで,ソースから自動生成できるようにする方法等が有効になる.

共同作業の作法

バグの報告

バグはMLに素早く投げるて共有知にするのが良い. 何か見付けたら,まずとりあえずそのバグが存在することを報告するのが良い. 解析して分析するのも良いが, 解決しない場合は,報告するのがよい. 解決しても報告を投げた方がよい.

報告に対しては何か早く返事を書くべき. 何も反応が無いと,そのバグは放置されてしまう可能性がある.

正しいバグ修正の確認

バグの修正が確実に行われたかどうかをチェックする必要がある. バグをチェックできるスクリプトを作成. できれば,ネットワークとかGTK+等が必要ないものが良い. そういうものをコンパイル時に自動実行できるようにする. できることなら,バグが確実に再発するスクリプトを作る.

automakeを使ったtestsuiteの設計方法としては,Makefile.amに

TESTS= test/test-one.sh \
      test/test-two.sh
EXTRA_DIST=   に $(TESTS)  を追加.
check_PROGRAMS= テストプログラムを実行するのにコンパイルしていることが必要なプログラムを列記.
TESTS_ENVIRONMENT= VAR=$(VAR) という形式で変数をわたす
    

を追加する.make checkを行うと,TESTSに列記してあるテストが全部成功したら 成功した,ということになる. check_PROGRAMSにかいておいたプログラムは,./programのようにして テストスクリプトから実行.

その上でバグを修正する. make check をして,直ったかどうかチェックする.

例えば,絶対に1を返すはずの hogefunc.cにある関数hogefunc()をテストしたい場合は,まず test-hoge.cを作成

#include "hoge.h"
main()
{
      if (hogefunc() != 1)
        exit (1);
      exit (0);
}
    

test/test-hoge.shを作成 (別にtest/ディレクトリ以下でなくても良いのだが, テストが多くなるとちょっとうっとうしくなるので,別ディレクトリにしておく)

#! /bin/sh

./test-hoge
    

そしてMakefile.amに以下を追加:

TESTS=test/test-hoge.sh
check_PROGRAMS=test-hoge
test_hoge_SOURCES=test-hoge.c hogefunc.c hoge.h
    

これで,autoconf automakeを再度実行して,./configure してmake check すると チェックが入る.make checkの出力例:

+ ./test-loadplugin '(text title hoge)'
Loading plugin text
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
+ ./test-loadplugin '(generation title hoge 10)'
Loading plugin generation
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
+ ./test-loadplugin '(singlepoint title hoge1 hoge2 0 0 10 10)'
Loading plugin singlepoint
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
+ ./test-loadplugin '(generation title hoge -10)'
Loading plugin generation
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
This plugin requires: 
a positive value for MAX-generation[generation] returned error in initialization exiting
error in loadplugin
+ ./test-loadplugin '(singlepoint title hoge1 hoge2 0 10 10 0)'
Loading plugin singlepoint
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
This plugin requires: 
minimum values to be smaller than maximum values.
[singlepoint] returned error in initialization exiting
error in loadplugin
PASS: test/test-loadplugin.sh
+ ./test-loadplugin-fail '(text title hoge)'
Loading plugin text
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
[text] returned wrong interface version 2 when 99999 was expected
error in loadplugin
+ ./test-loadplugin-fail '(generation title hoge 10)'
Loading plugin generation
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
[generation] returned wrong interface version 2 when 99999 was expected
error in loadplugin
+ ./test-loadplugin-fail '(singlepoint title hoge1 hoge2 0 0 10 10)'
Loading plugin singlepoint
$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $
[singlepoint] returned wrong interface version 2 when 99999 was expected
error in loadplugin
PASS: test/test-loadplugin-fail.sh
==================
All 2 tests passed
==================
    

このようにテストを追加しておくと,変更をして以前修正したバグが 再発しないことは確認できる.

コンパイル可能性の維持

常にコンパイルができるようにする. 自動コンパイル環境を整える.

duke.doshisha.ac.jpで2002年ころ稼働していた実例. 毎晩CVSからソースを取って来て, ./autogen.sh && ./configure && make && make check && make install を行うスクリプトが cron で稼働している. コンパイルに失敗したら,メールを出す.

#! /bin/bash
# autobuilding from CVS in duke!
set -e

if [ -e ${HOME}/buildlock ]; then
    echo "Locked!"
    exit 1
else
    touch ${HOME}/buildlock
    trap "rm -f ${HOME}/buildlock" exit
fi

WEBPAGEDIR="${HOME}/public_html/autobuild"
UPLOADDIR="${HOME}/public_html/autobuild/tar"
mkdir -p ${UPLOADDIR} || true

BUILDDIR="${HOME}/b-tmp/build"
INSTALLDIR="${HOME}/b-tmp/inst"
rm -rf "${HOME}/b-tmp"
mkdir -p "${BUILDDIR}" "${INSTALLDIR}"

cd "${BUILDDIR}"

case $(hostname) in
    duke)
	IAM_SSH=0
	export CVSROOT="/home/cvs";;

    *)
	export CVSROOT=":ext:duke.doshisha.ac.jp:/home/cvs"
	export CVS_RSH="ssh"
	eval $(ssh-agent)
	ssh-add
	IAM_SSH=1
	;;
esac

export CFLAGS="-I${INSTALLDIR}/include -O2 -Wall"
export LDFLAGS="-L${INSTALLDIR}/lib"
export LD_LIBRARY_PATH="${INSTALLDIR}/lib"

MAKE=make

for A in personal/dancer/CVSREPOSITORY/libdshconfig \
    personal/dancer/CVSREPOSITORY/dancer-xml \
    personal/dancer/CVSREPOSITORY/dlisp \
    personal/dancer/CVSREPOSITORY/dsh \
    personal/dancer/CVSREPOSITORY/dmachinemon \
    personal/dancer/CVSREPOSITORY/update-cluster; do
    (
	date
	set -x
	cvs export -Dnow -d "${A/*\/}" "${A}" 
	cd "${A/*\/}"
	if ./autogen.sh && \
	    ./configure -prefix="${INSTALLDIR}" && \
	    ${MAKE} check && \
	    ${MAKE} install && \
	    ${MAKE} dist ; then
	    echo "Build completed!!" 
	    date
	    cp "${A/*\/}"*.tar.gz "${UPLOADDIR}/${A/*\/}.tgz"
	else
	    echo "Build failed!!" 
	    exit 1
	fi
    ) > ${WEBPAGEDIR}/"${A/*\/}".log 2>&1 || \
	(
	for target in some-mail-address; do
	    (
		echo "\
Excerpt of building ${A/*\/}

Full log:
http://$(hostname -f)/~buildd/autobuild/${A/*\/}.log

======================================================================================================================
"
		cat ${WEBPAGEDIR}/"${A/*\/}".log | tail -40
echo "\
======================================================================================================================
"
		
	    )| \
		imput  --SMTPservers=mikilab.doshisha.ac.jp --User="$(hostname)-CVSBuildDaemon" \
		--fromdomain="dmirror.work.isl.doshisha.ac.jp" --name="cvs-build" \
		--Subj="Report on build failure of $A on $(hostname) from CVS" "${target}@mikilab.doshisha.ac.jp"
	done
    )
done

if [ "${IAM_SSH}" = 1 ]; then
    ssh-agent -k
fi

    

上川の coding standards,読みやすいコードを目指して

関数の長さを短く

関数はできるだけ短くする. 20行を越えたらダメ. 関数が長いと冗長になる. 複数のところで利用するものを関数というふうにするのではなく, 一つの処理の考え方のブロックを一つの関数にしたらよい.

機能最小限

機能は必要最小限で良い.必要になってから機能は追加する. ソースがあるんだから,いくらでも後から追加できる. プログラマは何でもできる巨大なソフトウェアを作りたがるが, 維持性や拡張性が犠牲になる.

doc++

doc++を利用して説明書を作成する. doc++では,ソースの中に特別な書式でコメントを埋め込む. javadocとかと同じ. doxygen等もある.doc++が一番手軽に動くので,とりあえず 使っている.

/** コメント */ というようにすると,ドキュメントが追加される. 出力例:

ソースは:

/**
   Function to send information to the immediate uplink,
   to be gathered using \Ref{dm_gatherinfo}.

   Other nodes will be able to see the information 
   sent with the TAG, for nodes which are connected to the 
   same uplink, and all the children of the nodes which are
   connected to the same uplink.

   It takes some time before the information is sent to uplink,
   namely the --sleep parameter given to dmachinemon-servent
   determines it.

   Also this function should fail if dmachinemon-servent
   has terminated for some reason, maybe with --Dieonload
   option.

   Unless --layers is specified when invoking dmachinemon-servent,
   the information that has been given with sendinfo will be available on
   all of the uplinks, with gatherinfo.


   @version 0.31.0 changed name to DNAS_Finalize from dm_Finalize, requires pointer to cdat
   @return 1 on error, 0 on success.
 */
int DNAS_sendinfo (const char * TAG, /** TAG to identify information */
		   const char * string /** The information string. */,
		   dm_commandoption* p2pcdat /** command line parameter option */
		   );
    

また,struct やclass等も:

/**
   Pairstring for data-string pair.
   Database is stored in this format.
 */
typedef struct dm_pairstring
{
  /** the key */
  char * stringtitle;
  /** the data */
  char * stringdata;
  struct dm_pairstring * next;
  /** lock */
  pthread_mutex_t lock;
} dm_pairstring;
    

このようになっているソースから文書を作成するには,例えば以下のようにコマンドを打ち込む:

      doc++ --dir doc *.[ch]
    

html以外にも,TeXや,DocBook等も生成できる. automakeのルールとして追加するのなら,configure.ac でdoc++があることを確認するところを追加:

AC_CHECK_PROGS([DOCPP],[doc++],[true])      dnl (無い場合は,trueというコマンドを利用)
    

そして,Makefile.am に以下のルールを追加:

clean-local:
      -rm -rf doc/ 

doc: ${srcdir}/*.[ch]
      @DOCPP@ --dir doc ${srcdir}/*.[ch]

    

ChangeLog

変更履歴がわかるようにする. そのためにはChangeLogを活用するのがよい. emacsでC-x 4 aとすると,ChangeLogが開く. 自分の名前と日付が記録される. どの日にだれがどのファイルにどのような作業をしたのか ということが分かる.

(setq user-mail-address "hogex@fugafuga.com") というように.emacsに設定することにより メールアドレスが正しく反映される. 自分の名前は,/etc/passwdの自分のところに本名を書く部分が あり,そこに記入されていれば,出るはず.

2002-10-15  Junichi Uekawa  <dancer@debian.org>

	* dmachinemon/libsocket.c (dm_tcp_host_setup): add hack with getenv and SETSOCKOPTHACK=yes option.

2002-10-14  Junichi Uekawa  <dancer@debian.org>

	* configure.ac: 0.32.1.cvs.5

	* tools/rnas/rnas-replay-single.c (main): daemonize.

	* tools/rnas/rnas-replay-multi.c (main): add optional daemonize
    

auto*ツールの使い方

autoconf等のツールを毎回実行するのは 面倒なので,./autogen.shというスクリプトを CVSレポジトリに登録しておく. CVSにautomake等が自動生成するファイルを登録すると, コンフリクトが起きやすくなるし,無駄に変更履歴が 膨大になる可能性がある.(違うバージョンのautomakeは 違う出力を吐くため) そのため,自動生成ファイルは.cvsignoreに 追加されるが,それをチェックアウトするたびに生成する 必要がある.そのためにも,このスクリプトは重要.

内容は:

#!/bin/bash
# Please run this application whenever checked out from CVS

libtoolize -c -f && aclocal && autoheader && automake --foreign -a -c && autoconf
    

libtoolを利用しない場合は, libtoolizeが無くてもよい.

この後

次回のこの講座は, Debianパッケージ構築方法について,講座を行う.


Junichi Uekawa

$Id: code-writing-guide.html.ja,v 1.13 2004/08/01 08:44:14 dancer Exp $