あるプログラマの日記

プログラマのメモ、出来事、考えたこと、勉強とかの雑記

思い出したように...

3年以上放置して思い出したように更新しています。

久しぶりにというか、書いていたことも忘却しかけている。しばらくすると放置状態になってしまうので、過去の仕事のことなどを日記に書き残していこうかなと思っています。長い間プログラマー・SEとして、仕事をしてきました。現在も継続しています。フリーになったのは今から13年ほど前で、今も現役のフリーでバリバリやっています。


フリーになるまでは、ソフトウェア開発会社, 所謂 Sler にいました。SIerは 2社経験しています。その前は電機メーカーのディーラー/販売会社で営業をしていました。経歴としては、もとはプログラマー・SEではなく営業職から転職したので、特殊だと言われることが多いです。


今、現在の仕事は自社の製品に搭載するソフトウエアの開発をおこなっています。なので仕事は製品が存在するかぎり継続しています。リリース後はプロジェクトはいったん終了しますが、基本はエンドレスです。

要求仕様問題プロジェクト その過程

 要求仕様に関しては、いつも確定しないまま実装に入ってインクリメンタルな開発を行うことが多かった。しかし、ついこの前の組み込み系通信システムの開発は完全なウォーターフォール型でした。結果はなんとか結合テストまで完了しましたが、その過程は散々でした。ウォーターフォール型では、設計に入る前に仕様が確定されることが前提ですが、T.B.D 項目以外にも仕様変更ではなく仕様漏れがけっこうありました。

 数人で実装しましたが、あいまいな仕様のまま実装した箇所は、結合試験ではまともに動作しなかった。当然といえば当然の成り行きです。短い期間で開発しなければならないプレッシャーがプロジェクトにあり、この圧力からプロジェクトリーダーは常にプロジェクトの建前上の進捗に執着して、実質的な問題点をおざなりにする傾向があったと思います。いや何が実質的な問題なのかもちゃんとわかっていなかったのではないかと思います。問題点をプロジェクトリーダーに報告してもその問題が根本的に解消されることはなかったし不明点が明確になることもなかった。開発時に、あまりにも不明点が多すぎましたが、それを問題としてクライアントにあげることができなかったのが致命的でした。クライアントへの進捗報告や打合せは、プロジェクトリーダーが行っていましたが、そこで問題点をクライアントに報告することをしていなかった。詳細仕様を設計段階で確定するとしても、それは要求仕様として本来確定すべきものであるとの認識がなかったように思います。
そのプロジェクトリーダーは結合試験前に、このプロジェクトから離脱しました。

【結論】

  • 不明点は不明点として顕在化させる。(報告する)
  • 短納期のプレッシャーに負けずに、本質的な問題を、把握して顕在化させる。(報告する)
  • 品質と納期に重点をおいて実現すべき機能に優先順位をつける。
  • それを踏まえて計画をたて、スケジューリングする。
  • その計画達成のための適切な開発手法を採用する。
  • 問題点の解決方法を検討する。

要求仕様化問題

3年以上も放置状態だったんですね。
久しぶりに更新します。

現在も、3年前とかわらずプログラマとして働いています。

この3年間の変化として、短期間ですが、違う開発のお仕事を経験しました。
未経験の開発環境、カテゴリでの開発でした。

  • 業務系のWindowsアプリケーションの開発(期間:約2か月)
  • 通信系組み込みシステムの開発(期間:6か月)

最初の業務系アプリケーションの開発も、次の通信系組み込みシステムの開発も
一番たいへんだったのは、仕様が決まっていない状態からの開発だったことです。
むかしからの要求の仕様化問題が、今現在も大きな問題であることを再認識しました。

開発工数の大半は、仕様が確定しないことによるものです。

それで、いままさに開発しようとしているシステムも同様の問題を抱えての
スタートになります。

java.nio.ByteBuffer

ByteBuffer は便利

DataInputStream の readFully みたいに指定サイズを
Byte バッファに読み込んで ByteBuffer にして返すメソッド。
この時のデータはリトルエンディアン

import java.nio.{ByteBuffer,ByteOrder}
..snip..
  def read[S <: { def read(b: Array[Byte], n: Int, len: Int): Int }](in: S)
    (size: Int): ByteBuffer = {
    val b = new Array[Byte](size)
    val bbuf = ByteBuffer.wrap(b)
    bbuf.order(ByteOrder.LITTLE_ENDIAN)
    var num = 0
    while (num < size) {
      val cnt = in.read(b, num, size - num)
      if (cnt < 0) throw new EOFException()
      num += cnt
    }
    bbuf
  }
..snip..

読みだしたデータを ByteBuffer にして扱うと
Int, Short, Double, 文字列 等の各種の型でデータを取り出して処理する際に便利。

..snip..
  val bbuf = read[InputStream](in)(32)

  println(" Int data : %d".format(bbuf.getInt(0)))
  val sdat: Int = bbuf.getShort(4)
  println(" Short data: %d".format(sdat))

  println(" Double data : %.10f".format(bbuf.getDouble(16)))
..snip..

ファイル保存した文字列を表示するときのエンコーディング指定

いまさらだがファイル保存した文字列のエンコーディング指定でハマった。
Shift_JIS でバイナリファイルに保存した文字列を読みだして
ローカルのエンコーディング(この時は EUC-JP)で表示する時のやり方

  def getStr(bb: ByteBuffer, pos: Int, size: Int): Pair[String,String] = {
    val enc = System.getProperty("file.encoding")
    val buf = new Array[Byte](size)
    for (i <- 0 until size) buf(i) = bb.get(pos + i)
    val hex = for (i <- 0 until size) yield { "%02X".format(buf(i)) }
    (hex.mkString, new String(new String(buf, "SJIS").getBytes(enc), enc))
  }

読みだしたデータを ByteBuffer で渡して表示エンコーディング文字列と
その文字列の16進数ダンプを返すメソッド。
データの ByteBuffer とその読み出し位置(pos)、読み出しサイズ(size)を
引数に指定して、読みだした文字列(表示エンコーディング)とその文字列
の16進数ダンプの文字列の Pair を返す。

..snip..
  val (hex, str) = getStr(bbuf, 32, 16)

  println(" str: %s".format(str))
  println(" hex: %s".format(hex)
..snip..

[Scala] java のアレは scala でどう書くの

基本的なことでも慣れないうちは、どう書けばよいかと、ググる日々..
scala でプログラミングし出して慣れない scalajava のアレはどう書くのか?
と簡単な処理でも勝手が違うので少し戸惑った事。

byte バッファ

java だと

byte[] buffer = new byte[size];

scala で 配列は Array[T] になるので byte 配列は

val buffer = new Array[Byte](size)

DataInputStream の readFully メソッドに java と同じ byteバッファとして指定可能。

import java.io.{File,FileInputStream,DataInputStream}
..snip..
val file = new File(filename)
val in = new DataInputStream(new FileInputStream(file))
val size = file.length.toInt
val buffer = new Array[Byte](size)
using[DataInputStream, Unit) { in => in.readFully(buffer) }
..snip..

break

scala 2.8 から使用可能。ライブラリで対応している。

breakable { .. } で囲ってそのブロック内のループ処理から break
してループを抜ける。

import scala.util.control.Breaks.{break, breakable}
..snip..
        breakable {
            while (true) {
                val ok = .. // 何かの処理
                if (ok) break
            }
        }
..snip..

break メソッドで、例外を投げて、breakable メソッドで
例外をキャッチして処理の流れを制御している。
正常処理の流れを例外で制御しているのが、普段、java のコードレビューで
やってはダメと指摘している手前、ちょっと.. 微妙かも

scala/util/control/Breaks.scala の中身(抜粋)

package scala.util.control

..snip.. // 省略

class Breaks {	
  private val breakException = new BreakControl
	
  /** A block from which one can exit with a `break''. */
  def breakable(op: => Unit) {
    try {
      op
    } catch {
      case ex: BreakControl => 
        if (ex ne breakException) throw ex
    }
  }
	 
..snip.. // 省略
	
  /* Break from dynamically closest enclosing breakable block
   * @note this might be different than the statically closest enclosing
   * block!
   */
  def break() { throw breakException }
}

object Breaks extends Breaks
	
private class BreakControl extends ControlThrowable
	

構造的部分型(Structural Subtyping)

型の階層構造とは関係なく特定のメソッドを定義して、このメソッドを持つ型として定義できる。
Rubypython のダックタイピングと同じようなもの

戻り値の型が Unit で引数無しの close メソッドを持つ型を構造的部分型として指定

  def using[S <: { def close(): Unit }, U](s: S)(f: S => U): U = {
    try f(s) finally s.close
  }

scala では type の抽象型定義 で C の typedef と似たような抽象型を定義することができる
type Hoge = Foobar

抽象型に構造的部分型を定義することも可能

  type Closeable2 = { def close(): Unit }

  def using[S <: Closeable2, U](s: S)(f: S => U): U = {
    try f(s) finally s.close
  }

長さによって、短い場合は直接指定して長くなる場合は抽象型に定義するとよさそう。
簡単なインターフェースは構造的部分型を使う。