bokusui について

ソフトウェアハウスでのPG・SEから始まり、10年近く勤めた金融系企業の社内SEを数年前にやめ、フリーランス時代を経たのち法人成りしました。システム開発の全工程をこじんまりとやり続けています。

フランク・ゲーリー展を見て感じたこと

 以前から、建築業界とシステム業界というのは共通点が結構あるな~と考えていました。作る対象物は違えど設計、構築といった仕事の進め方もそうだし、大手ベンダー=ゼネコン、多重請負とかの業界構造もです。で、今ミッドタウンの21-21DESIGNSIGHTでやっているフランク・ゲーリー展に行ってみようと思った訳です。
 正直、その斬新なデザインは賛否両論あるかと思いますが、筆者が驚いたのは、昔は模型を作っては壊してを繰り返して設計していたのが、今ではデザインコンセプトを模型で確認した後はコンピューターで3Dモデリングし、決められた予算範囲内でデザインを妥協することなく最適な資材構成となるようシミュレーションを繰り返して決定しているという事です。それにより、建築資材の数や搬入時期までを決める事で施工時のコストまでも最適化し、複数の施工業者が見積もっても誤差は0.5%とか。スゲエ!と思ってしまいました。ようするに、それを実現できるソフトウェアを開発した人であるということなんです。建築業界はまったく解らないですが、こういうやり方は一般的なのでしょうかね?国立競技場とかこの人に任せればよかったのにと思いますけど。。
 で、建築とシステム開発の違いについて、フランク・ゲーリー展を見た後に筆者が感じた所ですが、建築では、住宅からビルやホールとか構築対象物の規模が解りやすいという事と、建築での施工業者なる位置付けの仕事がシステム開発に存在しているか?という事です。
 システムの規模って、誰が見ても大規模なものもあれば、それ程でもないものってかなりあるように思いますが、その規模の判断が人それぞれな気がします。大抵大手ベンダーに依頼すれば大規模と見なされていませんかね?クリティカルなシステムだって、例えば5種類のデータしか扱わないのであればそれは対した複雑さは無いはずなんです。また、扱うデータ量が膨大だとしても実装されたシステムを多数のサーバに配置するだけなので、インフラ含めアプリもちゃんと設計されていれば開発自体のコストは何も変わりません。
 あと、施工業者という点では、所謂上流下流といった分け方をしている場合、プログラマーは施工業者みたいなものだという考え方を持っている人もいると思いますが、それを当てはめた方がよいケースは極少数でしかないと筆者は考えます。どちらかというとプログラマーは施工業者というより建築資材供給者の位置づけに近いと思いますが、システムでは1つの設計=1つのプログラムみたいなものです。同じものを量産するという概念がありません。すごく似ている機能が多数存在する事もありますが、それを一つまたは最小限にできないなら設計に問題があるはず。であれば、何を作るべきかを把握しているであろう設計者自身がプログラムを書く方が品質が高く、また分業する事によるコミュニケーションコストも最小化できるはずです。
 システム開発に施工業者が存在していないと考えの元では、建築家フランク・ゲーリーにとってのデザイン活動範囲は、システム開発全ての活動範囲と一致しており、構築対象物は違えど、システム開発におけるプログラミングはデザインという活動範囲に含まれているという事になります。施工業者的であるのは多数のサーバで構成された場合におけるインフラ側の仕事だけではないでしょうか?
 筆者の立場では、今は一人か極少数で開発をしているので、これは注文住宅を自身で設計・施工している工務店の大工みたいなものですかね。こういう戸建レベルの規模の開発案件って結構存在しているはずなんですが、欲しているものの実際の規模と受注側が考えているその規模のズレを注文側が感じるとる事を出来るかという所になります。でも、大規模と言われた物を小規模であると主張するのは結構タイヘンなんですよね。発言に対し全てのリスクを引き受ける気合い!がなければ、なかなか言える事では無いかもしれません。別の方法として、一つのシステムを戸建てレベルの集合体と分割することも出来ますけど、構築後の保守フェーズまで考慮するとこちらもタイヘンかなあ。。

ganymed ssh2を使ってダウンロード画面を作る

今作っているWebアプリケーションでサーバのログファイルをダウンロードしたいという要件があり、そこでganymedを使ってみることにしました。アプリケーションをデプロイしているサーバとは別に複数存在するサーバ上にあるログファイルを画面から選択してクリックでダウンロードという流れです。
 なお、サーバとログファイルが格納されているディレクトリ情報は別途DB等から取る形にして、下記はSFTPでファイルを取得する箇所の抜粋です。

POM.xml ganymed指定

    <dependency>
	  <groupId>ch.ethz.ganymed</groupId>
	  <artifactId>ganymed-ssh2</artifactId>
	  <version>262</version>
     </dependency>

画面に表示するログファイル情報

public class LogFile {
	
	/** ファイル名 */
	private String fileName;
	
	/** 更新日時 */
	private String upDateTime;
	
	/** ディレクトリ */
	private String directory;
	
	/** サイズ */
	private long fileSize;
	
	/** ホスト名 */
	private String hostName;

以下、ゲッタとセッタは割愛

SFTP処理
※サーバは一般的なID・パスワード認証です。

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SFTPv3Client;
import ch.ethz.ssh2.SFTPv3DirectoryEntry;
import ch.ethz.ssh2.SFTPv3FileHandle;

/**
 * SFTPファイルアクセスクラス by Ganymed SSH-2
 * */
@Stateless
public class SFTPFileAccess {
	private final int PORT = 22;
	private SFTPv3Client sftp = null;
	private Connection conn = null;
	
	/**
	 * リモートホストへログインします
	 * @param HostName リモートホスト名
	 * @param User ログインユーザー名
	 * @param Password ログインパスワード
	 * @throws IOException 
	 * 
	 * */
	public boolean loginHost(String HostName, String User, String Password) throws IOException{
		conn = new Connection(HostName, PORT);

		conn.connect();
        if (!conn.authenticateWithPassword(User, Password)) {
        	//エラー
        	return false;
        }
        return true;
	}
	
	/**
	 * リモートホスト接続をクローズします
	 * @throws IOException 
	 * 
	 * */
	public void logOutHost() throws IOException{
		if(conn != null){
			conn.close();
		}
        return;
	}

	/**
	 * リモートホスト上の指定ディレクトリのファイル一覧を取得する
	 * @param String RemoteDir ディレクトリ名
	 * @param List<LogFile> セットするファイル一覧
	 * @throws IOException
	 * */
	public void getSFTPList(String RemoteDir, List<LogFile> FileList) throws IOException {
		if(conn == null){
			return;
		}
        sftp = new SFTPv3Client(conn);
        
		List<SFTPv3DirectoryEntry> DirList = sftp.ls(RemoteDir);
		if ( FileList == null ){
			FileList = new ArrayList<LogFile>();
		}
        for (SFTPv3DirectoryEntry Entry : DirList ){
        	//ディレクトリ以外
        	if (!Entry.longEntry.startsWith("d") ){
	        	LogFile file = new LogFile();
	        	file.setDirectory(RemoteDir);
	        	file.setFileName(Entry.filename);
	        	file.setHostName(conn.getHostname());
	        	file.setFileSize(Entry.attributes.size);
                //ファイル更新時間
	        	Date tm = new Date(Entry.attributes.mtime * 1000L);
	        	file.setUpDateTime(DateUtils.format(tm, DateUtils.FORMAT_YYYY_MM_DD_HH_MM_SS));
	        	FileList.add(file);
	    		
        	}
        }
        return;
	}

    /**
     * SFTPファイル受信
     * @param remoteFile 受信元ファイル
     * @param stream 出力ストリーム
     * 
     * */
    public void getFile( String remoteFile, OutputStream stream) throws IOException{
		if(conn == null){
			return;
		}
    	if(sftp == null){
            sftp = new SFTPv3Client(conn);
    	}
        long remoteFileSize = sftp.stat(remoteFile).size;
        // open remote file with read only
        SFTPv3FileHandle sftpFileHandle = sftp.openFileRO(remoteFile);
        
        BufferedOutputStream bos = new BufferedOutputStream(stream);
        		
        byte[] buffer = new byte[1024];
        long offset = 0;
        int readLength = 0;
        while( (readLength = sftp.read(sftpFileHandle, offset, buffer, 0, buffer.length)) != -1){
          bos.write(buffer, 0, readLength);
          offset += readLength;
        }
        // flush & Close
        bos.flush();
        bos.close();
        sftp.closeFile(sftpFileHandle);
        
        // compare
        if (offset == remoteFileSize) {
        } else {
        	System.out.println("failed: file size is different from remote. remote:" + 
              remoteFileSize+", local:"+offset);
        }
        return;
    }
}

画面処理
※HttpServletResponseのOutputStreamに直ファイルを突っ込むので、一時ファイルを置いたりしません。

/**
 * ログダウンロード画面
 * */
@ManagedBean(name="LogDownload")
@SessionScoped
public class LogDownload implements Serializable {
	private static final long serialVersionUID = 1L;

	protected static Logger logger = LogManager.getLogger();
	
    @EJB
    private SFTPFileAccess ftp;
    
    private String selectedServer;

    private List<LogFile> fileList;
    
	public LogDownload() {
		fileList = new ArrayList<LogFile>();
	}

	public List<LogFile> getFileList() {
		return fileList;
	}

	public void setFileList(List<LogFile> fileList) {
		this.fileList = fileList;
	}

	public String getSelectedServer() {
		return this.selectedServer;
	}

	public void setSelectedServer(String selectedServer) {
		this.selectedServer = selectedServer;
	}

	/**
	 * ディレクトリ選択時にファイル一覧を取得
	 * */
	public void getList(){
		try {
			Map<String,String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
			String fileDir = params.get("fileDir");

			ftp.loginHost(selectedServer, dao.getServerLoginUser(selectedServer), dao.getServerLoginPasswd(selectedServer));
			
			fileList.clear();
			ftp.getSFTPList(fileDir,fileList);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * ファイルダウンロード
	 * */
	public void getFile(){
		Map<String,String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
		String fileName = params.get("fileName");
		String fileDir = params.get("fileDir");
	  
		try {
			HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance() .getExternalContext().getResponse();
			response.setContentType("application/octet-stream");
			response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

			OutputStream a = response.getOutputStream();
			ftp.getFile(fileDir +"/" + fileName , a);
			FacesContext.getCurrentInstance().responseComplete();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

xhtml
※ディレクトリリストの箇所は、上記ソース上割愛しています。
ディレクトリリストの左に置いたボタンクリックで、ファイル一覧を取得し、ファイル一覧のファイル名クリックでダウンロードします。

    <h:form id="listForm">
      <h:dataTable var="dirlist" value="#{LogDownload.directoryList}" rowIndexVar="rowIndex" class="table">
        <h:column>
          <f:facet name="header" >#{words.Label_LogList}</f:facet>
          <h:commandLink value="#{dirlist.INFO_VALUE}" actionListener="#{LogDownload.downLoad}">
            <f:attribute name="directory" value="#{dirlist.INFO_VALUE}"/>
          </h:commandLink>
        </h:column>
        <h:column>
          <h:commandButton id="listget" action="#{LogDownload.getList}" value="#{words.Label_FileListGet}">
            <f:param name="fileDir" value="#{dirlist.INFO_VALUE}" />
            <f:ajax render=":listForm:loglist"/>
          </h:commandButton>
        </h:column>
      </h:dataTable>
    
      <h:dataTable id="loglist" var="list" value="#{LogDownload.fileList}" styleClass="listTable">
        <h:column><f:facet name="header" >ファイル名</f:facet>
        <h:commandLink id="filelistName" value="#{list.fileName}" action="#{LogDownload.getFile}">
        <f:param name="fileName" value="#{list.fileName}" />
        <f:param name="fileDir" value="#{list.directory}" />
        </h:commandLink>
      </h:column>
      <h:column><f:facet name="header">ファイルサイズ</f:facet>
        <h:outputText value="#{list.fileSize}">
          <f:convertNumber groupingUsed="true" />
        </h:outputText>
      </h:column>
      <h:column><f:facet name="header" >ファイル更新日時</f:facet>
        <h:outputText value="#{list.upDateTime}"/>
      </h:column>
    </h:dataTable>    
  </h:form>

エラーハンドリングやサーバの切断、ファイルサイズの数値右寄せ等、細かい箇所がまだ出来ていませんが、とりあえず動きそうです。
最初、ログファイル情報のプロパティ名が先頭大文字とかだと、JSFでエラーになりちょっとハマりました。。

レルムの代わりにJASPICで

 今、企業内で少人数が使用するWEBアプリケーションをWebLogicで動かす前提で作ってますが、ユーザー認証についてどうしようかと要件を確認したところ、少人数使用のアプリなのでLDAP無し、WebLogicに登録・管理したくないのでWeblogicのレルムも使わないでとのこと。となると従来のDBにユーザーテーブル作ってユーザー管理画面とかも作る感じになります。
 ユーザーには数パターンあるうちの1つの権限を付与すれば使える画面と使えない画面が決まるので、基本的にURLで制御できる範囲です。で、いろいろ調べましたが、下記を参考にして、JASPIC(Java Authentication Service Provider Interface for Containers)というJavaEE7仕様に含まれているものを使ってみる事にしました。
http://arjan-tijms.omnifaces.org/2012/11/implementing-container-authentication.html
ここのSETP5にあるServerAuthModule実装のvalidateRequestに認証処理を書き込む形のようです。
下記のサンプルソースも参考になりました。
https://github.com/erik-wramner/YubikeyAuth/tree/master/yubi-jaspic-example

但し、WebLogicで動かす場合は、web.xmlにだけ設定すればよいだけでなく、weblogic.xmlにも同じような設定が必要みたいです。

web.xml

  <security-role>
    <description>管理者</description>
    <role-name>Managers</role-name>
  </security-role>
  <security-role>
    <description>利用者</description>
    <role-name>Users</role-name>
  </security-role>
  
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>manager page</web-resource-name>
      <url-pattern>/manage/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>Managers</role-name>
    </auth-constraint>
  </security-constraint>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>user page</web-resource-name>
      <url-pattern>/user/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>Users</role-name>
    </auth-constraint>
  </security-constraint>

weblogic.xml

    <wls:security-role-assignment>
      <wls:role-name>Managers</wls:role-name>
      <wls:principal-name>Managers</wls:principal-name>
    </wls:security-role-assignment>
    
    <wls:security-role-assignment>
      <wls:role-name>Users</wls:role-name>
      <wls:principal-name>Users</wls:principal-name>
    </wls:security-role-assignment>

デプロイするとそのインスタンス全体に影響し、開発環境では管理サーバで動かしているので管理コンソールに入れなくなります。。

カテゴリー: Java

フレームワークの性格

 ここ数か月、筆者はとあるシステムベンダーが構築したWebアプリケーションのフレームワークで作成された既存システムのソースを流用して、新規のWebサービスを作ってました。この既存システムは、新規開発から既に10年近く経過し、その間いろいろと変更・追加があったようです。フレームワーク自体はstruts1+Spring+ibatisという構成の上にさらにベンダー独自の実装で構成されたものです。そのベンダーさんに使用許諾を頂きましたが、特にリファレンスとかは無いのでソースを読みながらフレームワークを理解しながらの開発となりました。
 結果、3か月近く開発し、作ったアプリは現在ビジネス要件を満たし動く状態になってきましたが、こういう経験の無い枠組みの中で作るのは思ったよりも結構時間が掛かってしまうものだなあという印象です。一から作れと言われたらそりゃもっともっと時間が掛かるでしょうが。。
 最初、やはり時間の経過でしょうか、今さらこの構成?という感じでしたが、現在は良くできてるなあという思いもあります。ベンダーが作ったフレームワークって知ってる人からすると、結構時間が掛かる調べものの時間がないので、慣れてる人からすれば、ホントに工数を節減できたり、正確な工数を見積もったりできているんだとは思います。ただ、気持ち的には今さらstruts1系?とかここでの経験は他では使えないなどいろいろな感情が絡んでしまい、また、ソースの切り貼りみたいな作業も多く、モチベーションが低下してしまったのは否定できません。Javaの場合は流行り廃りがあるとはいえ、このようなフレームワークは、利用している方からするとシステム更新しない限りずっと使い続けるしかないのはしょうがない事ですが。
 時間の経過と言えば、最大なのはメインフレーム、いわゆるホストと呼んでいるものも一種のフレームワークと呼んでいいと思います。筆者の場合、最初がオープン系畑だったので、前職の時にホスト(zOS)と関わった時は、最初は違和感ありましたが、しばらくして良くできているなあと感じてきました。主にCobolソースとJCLの整然としたリソース構成は、雑然としたオープン系と比較するときれいに見えてきます。80桁×24行しかない画面もむしろ潔いよいです。。
 とはいえ、ホストのような自由度の無いガチガチなタイプがいいか、ちょっとぐちゃぐちゃ感はあるけど自由度のあるタイプがいいのかというと、もちろん筆者は後者の方が断然好きです。

TeraTermマクロでプロセスをKILL

UNIXサーバ作業の自動化として、特定のプロセスをKILLするマクロを書きました。
前提として
・停止対象のプロセスはJavaの常駐JOBが3つ
・プロセス停止前にチェック処理が必要
このチェック処理が複雑なので、マクロにしました。
下記は、サーバにログオンされた状態以降の抜粋です。チェック処理と最後のログオフは省略してます。
実際のツールはHTAで作業メニュー画面があり、画面操作でマクロを起動する構成にしました。

strdim OUTPUTBUF 500

; KILLする対象のプロセスがPSコマンドで表示される名前
strdim CHK_PROCESS_NAMES 3
CHK_PROCESS_NAMES[0] = 'jp.aaa.bbb.jobA'
CHK_PROCESS_NAMES[1] = 'jp.aaa.bbb.jobB'
CHK_PROCESS_NAMES[2] = 'jp.aaa.bbb.jobC'

; プロセスIDをゲットできたかのフラグ
CHK_PROCESS_RES = 0

timeout = 5

for iii 0 2
   
   CHK_PROCESS_NAME = CHK_PROCESS_NAMES[iii]
   ;; プロセスID取得subコール
   call GET_PROCESSID
   if CHK_PROCESS_RES <> 1 then
      messagebox '停止対象のプロセスが起動していません。'#13'状態を確認してくだい(OKで次プロセスの停止に進みます)' CHK_PROCESS_NAME
   else
      strlen CHK_PROCESS_ID
      if result = 0 then
         messagebox '停止対象のプロセスIDが取得出来ませんでした。'#13'状態を確認してくだい(OKで続行します)' CHK_PROCESS_NAME
      else
         ;; KILL前のチェック処理(省略・・)

         ;; プロセスKILL
         CMD = 'kill -9 '
         strconcat CMD CHK_PROCESS_ID
         sendln CMD
         wait '#' '$'

         ;; プロセスID取得subコール(Killしたのに残っていたら警告)
         call GET_PROCESSID
         if CHK_PROCESS_RES <> 2 then
            messagebox 'プロセスが停止できていません。'#13'状態を確認してくだい(OKで続行します)' CHK_PROCESS_NAME
         endif
      endif
   endif
next

;;マクロ終了
end

;;; sub プロセス状態確認 start
:GET_PROCESSID
CHK_PROCESS_RES = 2
OUTPUTCNT = 0
flushrecv

setsync 1
CMD = 'ps -ef | grep '
strconcat CMD CHK_PROCESS_NAME
sendln CMD

recvln
while result = 1
   recvln
   if result = 0 then
      break
   endif
   OUTPUTBUF[OUTPUTCNT] = inputstr
   OUTPUTCNT = OUTPUTCNT + 1
endwhile
setsync 0

if OUTPUTCNT > 0 then
   ;; Javaプロセスか確認(Grepでない)
   CHKSTR = '/bin/java'
   for i 0 OUTPUTCNT - 1
      strscan OUTPUTBUF[i] CHKSTR
      if result > 0 then
         ;; プロセスIDセット
         strcopy OUTPUTBUF[i] 10 8 CHK_PROCESS_ID
         CHK_PROCESS_RES = 1
         break
      endif
   next
endif

return
;;; sub プロセス状態確認 end

問題は発見するまでの方が時間がかかる

最近あまり読まなくなりましたが、以前はよくソフトウェア関連書を読んでいました。
関連書と言っても、テクニカルなものでなく、トム・デマルコとかワインバーグといった、辛口でウイットに富んだエッセイみたいなタイプの方です。
特にワインバーグの「ライト、ついてますか」がすごく好きです。これはソフトウェア関連書に分類されているので、大きい本屋のコンピューター関連書の陳列棚に置いていたりいなかったりですが、中身はまったくコンピューターと関係なく、サブタイトル「問題発見の人間学」の通り、真の問題を発見する事の重要性について面白い寓話で説明してくれている内容です。どっちかというとビジネス書とか自己啓発本の方に置いておけば、既に30年近く前の本とはいえ、結構売れるんじゃないかなと思うんですが、その手の本と比べるとちょっと値段が高いかな。。
仕事の打合せに出ていて、たまに問題が行ったり来たり跳ねたり飛んだりしてるなと感じる時によく思い出す本です。

税務署リトライ

先月提出をしなかった書類を税務署に出してきました。

・青色申告の承認申請書
・給与支払事務所等の開設申請書
・源泉所得税の納期の特例の承認に関する申請書

税務署ではパラッとチェックして一瞬で終わりました。

青色申告については、個人事業者の経験があるので、税制上の優遇があって有利だというのは分かるのですが、それ以外については、なんだかよくわからないので内容を整理してみます。

・給与支払事務所等の開設申請書
文字通り給与を支払うという行為が発生するにあたって、所得税の源泉徴収を行う事務所を開設しますという事で、この事務所宛に源泉所得税の納付用紙が送られてくるとの事。役員給与という形になるので一人法人でも必要になるとの事。
でも、後で気が付いたのですが、1ヶ月前に法人設立届出書を提出してしばらくした後に税務署から「源泉徴収のしかた」という冊子と納付書が届いていました。。税務署フライングしてません?この書類出さなくてもよかったのかな。。?

・源泉所得税の納期の特例の承認に関する申請書
これは本来毎月支払わなくてはならない源泉所得税を半年ごとにしてもらう為との事。
毎月払うなんて経理とか総務の人が居ない限り無理なんで、出さなくてはいけない申請書です。

一人法人として残りの手続きは年金周りと思いますが、もうちょっと情報収集してからにします。

人月の制約

ソフトウェア関係の書籍としてはあまりにも有名な「人月の神話」の冒頭に、プログラムをプログラミング製品(汎用性を持つデザイン、文書化、テスト等)にすると元の最低3倍のコストがかかり、プログラミングシステム(コンポーネントとしての使用に耐えうるようにするという事か)となるには、こちらも3倍かかるので、これら双方を兼ね備えたプログラミングシステム製品は結果9倍以上になるという記述があります。
で、現在ここでいうプログラミングシステム製品って何があるんだろうと考えますが、OSやJavaのようなプラットフォームしか思い当りませんね。
筆者のような日本にいる「システムエンジニア」と言われる人々は何を作っているのかと考えますと、企業内で使用されるシステムとかWEBサービスとかの構築がメインなんじゃないでしょうか?これらは、上記でいうところのプログラミング製品というものにも当てはまらないように思われます。文書化されてはいても、殆どの場合それを使う為では無く、どう作るか、またはどう作ったかという設計書としての文書だし、テストにしても到底あり得ないケースまで考慮してテストやったり、その為の実装したりってあまりやらないでしょ。
という事で、筆者の場合は特に個々の機能について見積もりをしない前提である為、「人月の神話」の世界にあるような大規模なシステム製品を作っている訳ではなく、参考になる所もあるけど、当てはまらない事が多いように感じました。
結局のところ、どこまでやるかがそのコストになるので、どこまで整理されたシステム構成にするか、どこまで完璧にテストするか、どこまで大多数の人が読める文書を作るかといった判断は、全て予定されたスケジュールと用意されたコストによってある程度決まってしまいます。結果的に「人月の神話」では無く、「人月の制約」の中で必要な機能を全てちゃんと用意した上で上記のような要素に対応するしかないのです。でも、それは全然悪い事では無いと思います。特に筆者のような仕事は、製品を作っているのではなく、エンジニアリングサービスを提供しているからです。
他者が書いた1システム全体のソースを読んでいると、そのような状況が何となく想像されますね。メンバー構成とか役割分担の仕方とか、テストで特に不具合が多かった箇所とか、アーキテクト的な全体を構成する立場の人がいたかとか。一番難しいのは全体の構成でしょうね。人によって読みやすいコードって多分違うので、コンセプトを理解した上でないと、良し悪しの判断さえ出来ないでしょう。

FilterでWeb要求・応答をログ出力してみる

今、リクエストを受信した後に対外接続を行うシステムを作ってますが、がつがつと外部に繋ぐのも気が引けるという事で、、そのシミュレータとして簡単なWebAPも作りました。
APサーバは、WebLogic11G。作っているAPの仕様上、POSTをサーブレット処理してます。
で、結局Oracleのページに乗っていたグッドなサンプルをそのままパクってみました(汗)
Oracle Containers for J2EE サーブレット開発者ガイド
Request処理は解るのですが、Responseの方はFilter内でラップするというちょっと自分では考え付かない内容でした。。
上記に載っている、FilterServletOutputStreamとGenericResponseWrapperは、深く考えずに同一パッケージ内にそのままクラスとして作ります。
で、Filterクラスを作ってログを出力します。よく使っているlog4j2でやってます。

package jp.esoro.http;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class PostFilter implements Filter {
	protected static Logger logger = LogManager.getLogger();
	
	@Override
	public void destroy() {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
 		try {
 			//要求
			HttpServletRequest req = (HttpServletRequest) request;
			
			BufferedReader body = new BufferedReader(req.getReader());
			String ret = body.readLine();
			//要求ロギング
			logger.info(req.getRemoteAddr()+"->"+req.getLocalAddr()+":"+req.getLocalPort()+req.getContextPath() + ":" + req.getMethod() + " RCV:"+ret);

			OutputStream out = response.getOutputStream();
			
			GenericResponseWrapper wrapper = 
			         new GenericResponseWrapper((HttpServletResponse) response); 
			//この後、サーブレット側処理に移ります
			chain.doFilter(request, wrapper);	
			
			//ラップしたwrapperからサーブレットの処理結果を取得
			byte[] data = wrapper.getData();
 			//OutputStreamに書き込んで応答
			out.write(data);
			out.close();
			//Shift-JISでString変換
			String result = new String(data, "Shift-JIS");
			//応答ロギング
			logger.info(req.getRemoteAddr()+"<-"+req.getLocalAddr()+ ":"+req.getLocalPort()+ req.getContextPath() + ":" + req.getMethod() + " SND:"+result);

		} catch (IOException e) {
			logger.error(e.getMessage());
		}
	}
	@Override
	public void init(FilterConfig arg0) throws ServletException {
	}
}

で、web.xmlに上記Filterをセットします

 	<!-- Filter -->
	<filter>
		<filter-name>PostFilter</filter-name>
		<filter-class>jp.esoro.http.PostFilter</filter-class>
	</filter>
	<filter-mapping>
        <filter-name>PostFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

最初サーブレットはアノテーション@WebServletで書きましたが、どうも11Gだとダメっぽいのでweb.xmlにservletをMappingする事にしました。シミュレータは本当に簡単なAPなので。。
開発環境は他のプロジェクトで使うWebLogic12cのままで11Gを別途用意しなかったんですが、実行環境が違うとデプロイに失敗して結構メンドクサイです。コンパイラーJavaのバージョンをちゃんと確認してなかったからなんですけど、、

TeraTermマクロのfileopenで失敗

去年作ったTeraTermマクロのサーバ作業の自動化ツールで1年以上問題無かった箇所でエラーになるとの事で見たら、端末のセキュリティレベルが変わっていてマクロのfileopenで失敗している事が判明。fileopenしているファイルが書き込めないように端末設定が変わっていたのが原因で、マクロのfileopenでは書き込めないとエラーになるんですね。
 で、マクロの仕様をネットで見たらfileopenの仕様に読み取り専用モードのオプションが4.85以降で追加されてるじゃないですか!
 かといって、ツールで使っているTeraTermマクロをバージョンアップするのも、20パターンくらいの作業を自動化しているのでちょっと躊躇。。
 作業自動化ツールをマクロで作ることにしたのは、基本ミドル・運用アプリ系の導入無し、複数サーバで同じような作業があるし、サーバ側に作業自動化シェルとか置くと、ここのサーバ運用的に変更の敷居が高いという環境上の要件からなんですが、マクロにしたらしたらで端末の設定変更でなくマクロを変えればいいじゃん的な発想に変わってしまうという、、まさにジレンマですね。変えやすいものは変われるけれど、それを維持するのは結構メンドクサイんです。。このメンドクサイという事実がシステムの開発・運用コストに直結しているハズなんですけどね。。