様々な基本になるサンプルを記録しています。

不定期更新です。

記事のサイドに使用している商品の紹介も掲載しているので、良ければご覧ください。

【コマンドプロンプト】FCコマンド

フリーソフトWINMERGEというソフトでよくテキストの比較を行うことがよくあるけど、
コマンドプロンプトでも比較するコマンドがある。

それがFCというコマンド

C:\Users\owner>fc /?
2 つのファイルまたはファイル セットを比較し、相違点を表示します。


FC [/A] [/C] [/L] [/LBn] [/N] [/OFF[LINE]] [/T] [/U] [/W] [/nnnn]
   [ドライブ1:][パス1]ファイル名1 [ドライブ2:][パス2]ファイル名2
FC /B [ドライブ1:][パス1]ファイル名1 [ドライブ2:][パス2]ファイル名2

  /A         相違する各部分の 1 行目と最後の行だけを表示します。
  /B         バイナリの比較を実行します。
  /C         英字の大文字と小文字を区別しません。
  /L         ファイルを ASCII テキストとして比較します。
  /LBn       連続する最大不一致行を指定行数に設定します。
  /N         ASCII の比較で行番号を表示します。
  /OFF[LINE] オフライン属性が設定されたファイルをスキップしません。
  /T         タブをスペースに変換しません。
  /U         Unicode テキスト ファイルとしてファイルを比較します。
  /W         連続した空白 (タブとスペース) を 1 つのスペースに圧縮して比較
             します。
  /nnnn      不一致発見後に確認する、一致すべき連続行数を指定します。

  [ドライブ1:][パス1]ファイル名1
             比較する最初のファイルまたはファイル セットを指定します。
  [ドライブ2:][パス2]ファイル名2
             比較する 2 番目のファイルまたはファイル セットを指定します。

コマンドスイッチを環境によって判断して使わないと
求める結果には到達できそうにないな、これ。

とにかく、比較をしてみる。

OracleのTRIM関数はある特定の文字しか、半角か全角のどちらかしかトリミングしないので
半角、全角のスペースの両方をちゃんとトリミングするファンクションを作って比較してみることにする。

一つ目、これは正常系

--MULTI_TRIM_1.sqlとする
CREATE OR REPLACE FUNCTION MULTI_TRIM (
    STR VARCHAR2
) RETURN VARCHAR2
    RESULT_CACHE
IS
    T_STR   VARCHAR2(32767);
BEGIN


    /*半角全角スペーストリミング*/
    T_STR := LTRIM(RTRIM(STR,'  '),'  ');
    RETURN T_STR;
EXCEPTION
    WHEN OTHERS THEN
        RETURN STR;
END;

二つ目。ちょっとバグらせてみる。

--MULTI_TRIM_2.sqlとする
CREATE OR REPLACE FUNCTION MULTI_TRIM (
    STR VARCHAR2
) RETURN VARCHAR2
    RESULT_CACHE
IS
    T_STR   VARCHAR2(32767);
BEGIN


    /*半角全角スペーストリミング*/
    T_STR := LTRIM(RTRIM(STR,'  '),'  ');
    RETURN W_STR;
EXCEPTION
    WHEN OTHERS THEN
        RETURN STR;
END;


FCコマンドを実行。
タブをスペースに変換しない、行番後を表示、オフライン属性をスキップしない。

fc /T /OFF /N TRIM_MULTI_1.sql TRIM_MULTI_2.sql


この結果、

ファイル TRIM_MULTI_1.sql と TRIM_MULTI_2.SQL を比較しています
***** TRIM_MULTI_1.sql
   11:      T_STR := LTRIM(RTRIM(STR,'  '),'  ');
   12:      RETURN T_STR;
   13:  EXCEPTION
***** TRIM_MULTI_2.SQL
   11:      T_STR := LTRIM(RTRIM(STR,'  '),'  ');
   12:      RETURN W_STR;
   13:  EXCEPTION
*****

なんで、TRIM_MULTI_2.SQLの拡張子が小文字なのに大文字になっているんだろうね。
比較できたからいいか。

OracleのMULTI_TRIMファンクションの記述はどこかで役に立つと思うので
参考にしてもられば良いと思う。

スクラム

過去に自分がプロジェクトを促進していたときに
まとめていたやつ。

アジャイル開発なんだけど、それに細かくいろいろ役割を決めて、ぐるぐるスケジュールを回してく手法。
それがスクラム開発。

字汚いのは勘弁したいところだけど、
システムの早い変化には対応できる手法だと思っている。
もちろんレビューとかはしっかりやっておかなければいけないし、
なによりも、役割や権限が決められていたとしても、上下関係があると進まない。
どうやって互いに協力しあっていくかが、プロジェクトの将来を左右していくことになりそうだ。

個人としてこれの実績があるかというと、実績としてはあるけど
手法問わず、要件定義と基本設計をしっかりチーム全体にレビューすることが重要ではないかと思う。
プロジェクト規模にもよるけど、システム基盤が確立し、詳細設計とプログラミングを反復させていけば、人の負荷も少ないし、工数の負荷分散も難しくない。
・・・と、これらのことがしっかりできた上での実績になると思う。

でもスクラム開発手法をやってた当時は、意思決定と行動を早く行うことができたので、自分が客先で打ち合わせしながら要望を聞きつつ、さらにシステムの変更を打ち合わせ中に行うことができた。
そして、その場でユーザーに理解が得られることができたので、持ち帰り作業がなくなり、次のステップに早く移行することができたということは結構あった。
100%完成していなくても、ユーザーに早い段階で見せることができるというメリットが非常に大きかったと思う。
これによって、システムの問い合わせが減ったのと、ドキュメントも反復の計画内でのちょびちょび更新程度になったので、
プロジェクト後半が非常に楽で、システム本番稼働の日が担当者との食事会になってたような記憶がある。

f:id:karinto441:20181026234937p:plain


f:id:karinto441:20181026234944p:plain


f:id:karinto441:20181026234945p:plain

これにはまだ先の話があるけど、
座学にようなものになりそうなので、その先は自分自身で調べて
使えるのであれば、使ってみてはどうかなと思う。

【Oracle】データ件数におけるページ分けの計算

タイトルの表現がちょっとよく分からなかったら申し訳ないのだが、
よく、WEBとかで〇〇件中、XX~YY件とかって分けて表示することがあると思う。
今回はその総件数の中で、どのページにどこからどこまでのデータを何件表示されるかという計算ロジックを作ってみた。

これも使用するTYPEは以下を参照

fubukin.hatenablog.com

create or replace FUNCTION PAGE_CALC (
/*
引  数:       
DATA_COUNT:データ総件数
CUR_PAGE  :現在のページ
VIEW_COUNT:表示する件数

戻り値:          

VALUE01:総ページ数
VALUE02:現在のページ数
VALUE03:現在のページの最初の行位置
VALUE04:現在のページの最後の行位置
*/

        DATA_COUNT   NUMBER,
        CUR_PAGE     NUMBER,
        VIEW_COUNT   NUMBER
    ) RETURN STD_VALUE4_TABLE
        PIPELINED
    IS
    /* レコード格納定義 */
        W_RET_REC   STD_VALUE4;
    BEGIN

        W_RET_REC                  :=STD_VALUE4('0','0','0','0');

    /*総ページ数 データ件数÷表示件数の商を切り上げ*/
        W_RET_REC.VALUE01       := TO_CHAR(CEIL(DATA_COUNT / VIEW_COUNT));
    /*現在のページ そのまま*/
        W_RET_REC.VALUE02       := TO_CHAR(CUR_PAGE);
    /*現在のページが最終ページ以上のページだったら最終ページに合わせる*/
        IF
            TO_NUMBER(W_RET_REC.VALUE02)   >= TO_NUMBER(W_RET_REC.VALUE01)
        THEN
            W_RET_REC.VALUE02   := TO_CHAR(W_RET_REC.VALUE01);
        END IF;
    /*最初の行位置 表示件数 * (現在のページ-1) + 1*/
        W_RET_REC.VALUE03      := TO_CHAR(VIEW_COUNT * ( TO_NUMBER(W_RET_REC.VALUE02) - 1 ) + 1);
    /*最後の行位置 表示件数 * 現在のページ*/
        W_RET_REC.VALUE04        := TO_CHAR(VIEW_COUNT * TO_NUMBER(W_RET_REC.VALUE02));
    /*最後の行位置が最終行よりも上回ったら最終行に合わせる*/
        IF
            TO_NUMBER(W_RET_REC.VALUE04) >= DATA_COUNT
        THEN
            W_RET_REC.VALUE04   := TO_CHAR(DATA_COUNT);
        END IF;
    /*表関数にレコード追加*/
        PIPE ROW ( W_RET_REC );
        RETURN;
    /*エラーの場合は0を返す。*/
    EXCEPTION
        WHEN OTHERS THEN
            RETURN;
    END;

f:id:karinto441:20181026233820p:plain

この計算情報って、どんなプログラムでも最初から作ると結構、間違いやすい。
これを参考にして違うプログラムでも作ってみるといいかもしれない。

【Oracle】よくあるSPLIT関数を実装する。

さすがにそのままの仕様を実現することはできなかったが、
かろうじてそれに近いものを公開しようと思う。

今回使用しているTYPEは以下を参照
fubukin.hatenablog.com


create or replace FUNCTION SPLIT_REC (
/*
引数:
STR             :区切り文字の入った文字列
STR_SPLIT       :判定対象文字列
PUSH_NUM        :STR_SPLITの文字列の何番目で分割するか設定(デフォルトは最初の文字)

戻り値:
VALIE01:要素番号
VALUE02:値
VALUE03:値の文字数
VALUE04:値のバイト数

*/
    STR         VARCHAR2,
    STR_SPLIT   VARCHAR2,
    PUSH_NUM    NUMBER DEFAULT 0
) RETURN STD_VALUE4_TABLE
    PIPELINED
IS
    W_RET_REC   STD_VALUE4;
    W_STR       VARCHAR2(32760) := STR;
    T_STR       VARCHAR2(32760) := '';
    I           NUMBER(38);
    VSTARTPOS   NUMBER(38);
    VENDPOS     NUMBER(38);
BEGIN
    I := 1;
    IF
        STR IS NULL
    THEN
        RETURN;
    END IF;
    LOOP
        W_RET_REC := STD_VALUE4(' ',' ',' ',' ');
        VSTARTPOS := 1;
        VENDPOS := INSTR(W_STR,STR_SPLIT,VSTARTPOS,1);
        IF
            ( VENDPOS = 0 )
        THEN
            W_RET_REC.VALUE01 := I;
            W_RET_REC.VALUE02 := SUBSTR(W_STR,VSTARTPOS);
            W_RET_REC.VALUE03 := TO_CHAR(NVL(LENGTH(W_RET_REC.VALUE02),0) );
            W_RET_REC.VALUE04 := TO_CHAR(NVL(LENGTHB(W_RET_REC.VALUE02),0) );

            PIPE ROW ( W_RET_REC );
            RETURN;
        ELSE
            VENDPOS := VENDPOS + PUSH_NUM;
        END IF;

        W_RET_REC.VALUE01 := TO_CHAR(I);
        W_RET_REC.VALUE02 := SUBSTR(W_STR,VSTARTPOS,VENDPOS - VSTARTPOS);
        W_RET_REC.VALUE03 := TO_CHAR(NVL(LENGTH(W_RET_REC.VALUE02),0) );
        W_RET_REC.VALUE04 := TO_CHAR(NVL(LENGTHB(W_RET_REC.VALUE02),0) );

        PIPE ROW ( W_RET_REC );
        T_STR := SUBSTR(W_STR,VENDPOS);
        W_STR := SUBSTR(W_STR,VENDPOS + 1);
        
        IF
            T_STR IS NULL
        THEN
            EXIT;
        END IF;
        I := I + 1;
    END LOOP;

    RETURN;
EXCEPTION
    WHEN OTHERS THEN
        W_RET_REC.VALUE01 := 0;
        W_RET_REC.VALUE04 := SQLERRM;
        PIPE ROW ( W_RET_REC );
        RETURN;
END;

デルミタ文字を1文字指定でしかできなかったので、
第三引数にデルミタ文字が2文字以上だったら、デルミタ文字のどの1文字で区切るかを指定できるようにした。
文字の分割はいろいろなパターンがあって、その文字が実際にデータとして使われる文字になってしまうと色々と面倒なことになる。
その為に今回の第三引数を使って対応を広くできるようにした。

f:id:karinto441:20181026232548p:plain

分けるのであれば、第三引数はいらないのだけど
カンマでわけるときにダブルクォーテーションで囲んだ中にカンマがあったら、
そこで分かれてしまう可能性がある。
ダブルクォーテーションを除去するまでには行けなかったけど、
あとはreplace関数でそれぞれの要素に対して、消してあげればいい。

【MAGICK.NET】PDFを画像に変換する

昔の話だけど、ImageMagickというオープンソースを使ってPDFの変換を試みたことがある。
しかし、リファレンスが全く無かったのと、メモリリークが起こってしまい断念した。
今はImageMagickではなく、Magick.NETっていうオープンソース用のライブラリが公開されている。

以下からダウンロードできる。
Release Magick.NET 7.9.0.0 · dlemstra/Magick.NET · GitHub

ただ、これを動かすにはghostscriptをインストールする必要がある。
ポータブル版ではダメだった。
サービス終了のお知らせ


そんなわけで、それを使ってPDF変換をやってみることにした。
動作環境は.NET Framework4.0で、言語はVisual Basic.NET。
GhostScriptのバージョンは9.25。
Magick.NETのバージョンは、「Magick.NET-7.9.0.0-Q16-x64」を使用した。
x64のライブラリじゃないとメモリがパンクするような気がするし。

ちなみに
「Magick.NET-Q16-x64.dll」は参照として追加。
「Magick.NET-Q16-x64.Native.dll」はexeと同じ場所に配置した。
もちろん、ターゲットCPUはx64。

ソリューション構成はこんな感じ。
f:id:karinto441:20181024090925p:plain
「Magick.NET-Q16-x64.Native.dll」は参照しないで置いとくだけ。
ちなみにビルドと一緒にコピーしてしまえば楽なので、「常にコピー」にした方がいい。
Visual Studio2017でやってるけど、基本的にIDEは下位バージョンでも変わらない。

とりあえず、FORMアプリケーション作って、LOADイベントで動かしてみる

Imports ImageMagick
Public Class Form1


    ''' <summary>
    ''' PDFをPNGイメージに変換する
    ''' </summary>
    ''' <param name="pdf_name">PDFファイルパス</param>
    ''' <param name="Export_name">返還後のファイル名(拡張子はいらない)</param>
    Public Function PDFTOPNG(pdf_name As String, Export_name As String, ByRef Optional ERRMSG As String = "") As Boolean

        '読み込み設定クラスのインスタンス化
        Dim settings As New MagickReadSettings()
        '解像度の設定
        settings.Density = New Density(300, 300)

        '全部読み込むとメモリが足りなくなるので1ページずつ読み込むために最初のインデックスを設定
        settings.FrameIndex = 0

        'カラータイプの設定
        settings.ColorSpace = ColorSpace.sRGB
        settings.ColorType = ColorType.TrueColorAlpha

        'アンチエイリアスは無効にしておく
        settings.StrokeAntiAlias = False
        settings.TextAntiAlias = False


        'クラスのインスタンス化
        Dim images As New MagickImageCollection()

        Try

            While True

                '再インスタンス化
                images = New MagickImageCollection()

                'PDFの読み込み
                images.Read(pdf_name, settings)

                'EXIF情報を削除
                images.Item(0).Strip()

                '解像度を大きくしているので半分にリサイズする
                images.Item(0).Scale(percentage:=50)

                'ページ変数のカウントアップ
                settings.FrameIndex += 1

                'PNG形式で保存設定
                images.Item(0).Format = MagickFormat.Png

                'PNGファイル書き出し
                images.Item(0).Write(Export_name & CStr(settings.FrameIndex) & ".png")

                'ページの明示的開放
                images.Item(0).Dispose()

                'コレクションの明示的開放
                images.Dispose()

                'メモリがいっぱいになる可能性があるので明示的に開放したリソースを
                'ガーベジコレションを強制的に起こすことによって解放
                GC.Collect()


            End While

        Catch ex As Exception

            'エラーとして検知されるとき、読み込んだPDFのページ数以上で読み込みを行った場合は終了とする
            If InStr(ex.Message, "Requested FirstPage is greater than the number of pages in the file") <> 0 Then

                ERRMSG = ""
                Return True

            Else

                ERRMSG = ex.Message
                Return False

            End If

        Finally

            '念のため、最後に解放しておく
            images.Dispose()
            GC.Collect()

        End Try


    End Function

 'ロードイベント
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load

        Dim errmsg As String = ""

        If Not PDFTOPNG("C:\temp\test.pdf", "PDFIMG", errmsg) Then

            MsgBox(errmsg)

        End If

    End Sub

End Class

一応、ネットで公開されているソースは基本的にC#で書かれていることが多いので、
参考にしたC#ソースをVBに書き換えて記述している。
世の中はやっぱり、C#なんだなと思った。


このソースは1ページずつ読み込んでPNG形式に保存している。
一気に読み込みこともできるんだけど、使用メモリが尋常じゃないほど増える。

62ページのPDFファイルがちょうどあったので、一気に変換しようとしたら
3GB以上もメモリを使用されてちょっと焦った。

PDF変換メソッドをクラスライブラリとかにすれば、ASP.NETでも組み込むことができるだろうな。
画像の劣化との戦いもあるけど、解像度をどう調整して、リサイズしていくかで奇麗になりそうだ。

また、PNG形式以外でも保存できるし、イメージをPDFに変換することもできるので、
結構、出来ることは広い。

でも・・・
これ、内部でghostscriptのコマンドが呼ばれてるだけなんだよね。
それが分かっていれば、ghostscript単体のみでなんでも行けてしまいそうな気がするな。

ウォータフォール型開発とアジャイル型開発

ウォータフォール型とアジャイル型、

どちらが生産性が良いものかと聞かれても、それは一概には言えない。

プロジェクト規模や分野等に左右されてしまうものなので、どんな開発計画であっても柔軟に計画を考えて行くことが必要。

しかし、開発モデルを基盤とした方が計画が立てやすいので、この二つを比較して考えてみる。

 

結果的な部分からまず考えると、個人的にはウォータフォール型は「最後に動くものができる」と考え、アジャイル型は「動くものが徐々にできあがり、成長する」と考えている。

このように言葉で言ってみると、アジャイル型の方が良い印象に聞こえるかもしれない。

ただ、開発工程の進め方として、アジャイル型は「終わりが見えない」という部分があるのと、ウォータフォール型は「終わりが見えている」というところもあり、ユーザーの要求が事前に全て収集・把握されていて、工程が戻ることがなければ、ウォータフォール型が良いと考えられる。

 

例を言うと、さまざまなプロダクト製品の開発や機械系の組み込み開発においては、仕様がはっきり決まっていることが多いので、ウォータフォールの方が向いているのではないかと思う。

しかし、業務系の開発においては、短い周期でシステムの運用がコンプライアンスや組織構成によって変わることもある為、アジャイル型の方が向いている。

 

基本的にアジャイル型はフィードバックを受けながら、ソフトウェアを完成させていくので、変化の対応に適している。

 

分野や納期等をしっかり考慮し、どういう型に当てはめていくかを決めて、プロジェクト計画を立案していくことが望ましい。

 

もちろん、開発モデルはこの二つ以外にもあるので、受けた要求、プロジェクトがどの開発モデルに適しているかはしっかり調査してみると良いと思う。

 

 

ソフトウェア技術者の職業性ストレス

書名:ソフトウェア技術者の職業性ストレス

著者:藤垣裕子

発行:労働科学研究所出版部

 

これはかなり昔の本だけど、その古書を読んだことがあるので簡単に書いておく

 

 

一般に言われている開発工程としては以下のような工程がある。

 

①要件定義

 

②基本設計

 

③詳細設計

 

④プログラミング(コーディング)

 

⑤テスト

 

⑥メンテナンス(保守)

 

これらの①~⑥で大きなストレス要因となるとすると、

どの工程もストレス要因となるのはもちろんのことだろう。

 

実際に考えてみれば、納期や要求品質、開発環境の状態やアプリケーション分野によって違うのは当然のことだと思う。

 

例えば、フロントエンド開発での操作面において、運用の流れに影響しないような操作のバグであれば、ストレスとしては小さい。

しかし、金融や販売系のシステムで数値を確実に合わせなければならないものであれば、不具合は許されないのでストレスは大きい。

 

とはいえ、ストレスを考えると抽象的な部分で話をまとめてしまうと、具体的な作業負荷と精神的負担がどこにあるかが見えて来ないので、

①~⑥の各工程での作業負荷(以下、負荷とする)と精神的負担(以下、負担とする)を考えてみることにする。

 

①要件定義と②基本設計

負荷としては小さいが、負担としては仕様が見えない、理解できているのか不安等、負担は高い傾向にある

 

②詳細設計

 一つ一ついろいろなことを決めていかなければいけないので、負荷としては高い方になるが、ゲームを解くような面白さがあるということがあるので負担としては小さい。

 

③プログラミング(コーディング)

結構、大変な工程に見えるかもしれないが、詳細設計がしっかりしていれば、単調になりがちであるため、負担は小さいが単純作業を繰りかえしていくということでは負担は多少なりともあるだろう。

 

 

デバッグ

誰もが一番やりたくない工程。

問題解決に苦しんだり、納期に追われてしまうことが多々ある為、負荷も負担もかなり大きい。

 

⑤テスト

ほとんどが力作業に近いため、負荷としては大きいが、テストケースがはっきりしていれば単純作業になりやすい。

しかし、テストで不具合があればデバッグしなければいけないので、負担はある。

 

⑥メンテナンス(保守)

負荷と負担はトラブル内容によるが、システムダウンや法外な要求があると負荷も負担も非常に大きくなる。

 

さて、システム開発の最大目標としては、顧客満足になると思うが、

その顧客満足に到達するまでには、①~⑥の工程をしっかり突破しなければならない。

 

ただ、その中でその工程を突破していくのは、あくまでも「人」だということを忘れてはならない。

ストレスが溜まれば、モチベーションが落ち、生産性も落ちる。

その悪循環をどのように解決していくかを考える為に、リスクマネジメントをしっかり考慮したうえでプロジェクト計画を進めていく必要がある。

 

また、「ストレス要因になる」ということを、リスクの一つとして考える必要がある。

それにより、各工程でつまづいてしまえば、最後のメンテナンス(保守)の工程が全ての工程をけん引することになってしまい、長く続いて終わらないものになるだろう。