Vue2 から Vue3 へ移行する ボタンイベント編

前回の続き

こんな風に CRUD 機能が付いた画面を Vue2 から Vue3 へ移植してみる(実質的には Vue3 から Vue2 形式を書き起こしている)。通常ならば、上部のリスト部分とか、下部の編集項目のところを Vue コンポーネント化するところなのだが、その前段階として、1枚のベタの *.vue ファイルに書き起こす。

template 部分

template>
    <div>
        <h1>Mos Main by vue2</h1>
        <div>
            <ul>
            <li v-for="it in items" :key="it.id">
                    {{ it.id }} : <a @click="onClickItem(it)">{{ it.title }}</a>
                </li> 
            </ul>
        </div>
        <hr />
        <button @click="onClickCreateItem" class="btn btn-primary">新規作成</button>   
        <button @click="onClickUpdateItem" class="btn btn-secondary">更新</button> 
        <button @click="onClickDeleteItem" class="btn btn-danger">削除</button>
        <hr />
        <div>
        <!-- カテゴリ情報を表示 -->
            <div v-if="mode == 0">
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td><td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td><td>{{  cur.title }}</td>
                        </tr>
                        <tr>
                            <td>image: </td><td>{{  cur.image }}</td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <div v-if="mode == 1">
                <div>新規作成のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                    <tr>
                        <td>title: </td>
                        <td><input v-model="cur.title" placeholder="タイトル" /></td>
                    </tr>
                    <tr>
                        <td>image: </td>
                        <td><input v-model="cur.image" placeholder="画像" /></td>
                    </tr>
                </tbody>
                </table>
                <button @click="onClickCommit" class="btn btn-primary">登録</button> 
                <button @click="onClickClear" class="btn btn-secondary">クリア</button>
            </div>
            <div v-if="mode == 2">
                <div>更新のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td>
                            <td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td>
                            <td><input v-model="cur.title" placeholder="タイトル" /></td>
                        </tr>
                        <tr>
                            <td>image: </td>
                            <td><input v-model="cur.image" placeholder="画像" /></td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
                <button @click="onClickCommit" class="btn btn-primary">登録</button> 
                <button @click="onClickClear" class="btn btn-secondary">戻す</button>
            </div>
            <div v-if="mode == 3">
                <div>削除のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td><td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td><td>{{  cur.title }}</td>
                        </tr>
                        <tr>
                            <td>image: </td><td>{{  cur.image }}</td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
                <button @click="onClickDelete" class="btn btn-danger">削除する</button> 
            </div>
        </div>
    </div>
</template>

リストの部分は共通にしておいて、CURD 機能を v-if で切り分ける。初心者っぽいけど、新人教育では初心者なのでこれで問題なし。最初は v-if で区切っておいて、徐々に Vue コンポーネントに慣れていくというスタイルにする。いきなり Atomic Design に進むのもアリなんだろうけど、歴史的な経緯を追っておくほうが体系的で覚えやすい。なによりも、業務システムが Vue2 と Vue3 が混在している状態なので両方覚えるのは必須なのだから。

            <div v-if="mode == 2">
                <div>更新のモード</div>
                <table class="table">
                    <thead>
                        <tr>
                            <th>項目</th>
                            <th>値</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>id: </td>
                            <td>{{  cur.id }}</td>
                        </tr>
                        <tr>
                            <td>title: </td>
                            <td><input v-model="cur.title" placeholder="タイトル" /></td>
                        </tr>
                        <tr>
                            <td>image: </td>
                            <td><input v-model="cur.image" placeholder="画像" /></td>
                        </tr>
                        <tr>
                            <td>作成日時: </td><td>{{  cur.created_at }}</td>
                        </tr>
                        <tr>
                            <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                        </tr>
                    </tbody>
                </table>
                <button @click="onClickCommit" class="btn btn-primary">登録</button> 
                <button @click="onClickClear" class="btn btn-secondary">戻す</button>
            </div>

更新するときの画面は

  • テキスト入力(input)を v-model で参照させる(双方向なので)
  • ボタン(button)のイベントは @click で渡す

というノーマルな形にする。:value を使うとかトリッキーな方法もあるのだが、ここはノーマルに v-model を使ったほうが移植性がよい。

vue2 では v-if に即値を使ってしまっているが、vue3 では enum が使える

        <div v-if="mode == MODE.UPDATE">
            <div>更新のモード</div>
            <table class="table">
                <thead>
                    <tr>
                        <th>項目</th>
                        <th>値</th>
                    </tr>
                </thead>

vue2 でも const すれば定義値を使うこともできるけど、1画面内ならば即値でも十分なのでこの形式にしておく。

vue2 の script コード

import axios from 'axios'

export default {
  name: 'MosMain',
  props: {
  },
  data() {
    return {
        items: [],  // カテゴリ一覧
        cur: {},    // 選択したカテゴリ
        mode: 0,    // 表示モード
    }
  },
  mounted() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        this.items = response.data
    })
  },
  methods: {
    getCategoryById( id ) 
    {
        axios.get(`http://localhost:8000/api/category/${id}`)
        .then ( response => {
            console.log( response )
            this.cur = response.data
        })
    },
    // 新規作成のAPIを呼ぶ
    createCategory( item ) {
        console.log( "called createCategory" )
        // TODO: POST を記述する
        return 
    },
    // 更新のAPIを呼ぶ
    updateCategory( item ) {
        console.log( "called updateCategory" )
        // TODO: PUT を記述する
        return 
    },
    // 選択項目を新規作成する
    onClickCreateItem() {
        console.log( "onClickCreateItem " + this.cur.title )
        this.mode = 1
        this.cur.id = 0
        this.cur.title = ""
        this.cur.image = ""
    },
    // 登録ボタンを押下
    onClickCommit() {
        console.log( "onClickCommit " + this.cur.title )
        console.log( "onClickCommit " + this.cur.image )
        if ( this.mode == 1 ) {
            // 新規作成の場合 POST を呼ぶ
            createCategory( this.cur )
        } else {
            // 更新の場合 PUT を呼ぶ
            updateCategory( this.cur )
        }
    },
    // クリアボタンを押下
    onClickClear() {
        console.log( "onClickClear ")
        if ( this.mode == 1 ) {
            // 新規作成の場合
            this.cur.title = ""
            this.cur.image = ""
        } else {
            const original = this.items.find( x => x.id == this.cur.id )
            if ( original ) {
                // 更新の場合
                this.cur.title = original.title
                this.cur.image = original.image
            }
        }
    },
    // 
    onClickDelete() {
        console.log( "onClickDelete ")
        // 削除 API を呼び出す
        deleteCategory( this.cur )
    },

    // リストで項目を選択
    onClickItem( item )
    {
        console.log( "onClickItem " + item.title )
        this.getCategoryById( item.id )
    },  
  
    /**
     * 選択項目を更新する
     */
    onClickUpdateItem() {
        if ( !this.cur.id ) {
            console.log( "ERROR: onClickUpdateItem cur.value.id is null" )
            return 
        }
        console.log( "onClickUpdateItem " + this.cur.title )
        this.mode = 2
    },

    /**
     * 選択項目を削除する
     */
    onClickDeleteItem() {
        if ( !this.cur.id ) {
            console.log( "ERROR: onClickDeleteItem cur.value.id is null" )
            return 
        }
        console.log( "onClickDeleteItem " + this.cur.title )
        this.mode = 3
    },
  }
}

その壱と同じように、

  • export default で公開する
  • props, data, mounted, methods ブロックで区切る
  • ボタンのイベントとボタンから呼び出す関数は、methods 内に突っ込んである

data を参照するのに、this.* を付けないといけない。「付けないといけない」と書いたが、C++ や C#, Java などを使っていると、クラス内でのフィールド変数は this.* を付けたほうが解りやすいので、こういう風に書くことが多い。最近の Visual Studio 2022 では this なしが推奨されてるけども、this を付けるほうが外部なのか内部なのかわかりやすいのと、this.* としたところでインテリセンスが効くので、便利だったりする。ただし、最近の傾向としては、できるだけ外部の変数を参照しない=クラス内あるいは関数内だけでおさまるようにする、関数型っぽい書き方が推奨されているので、this と打つのは「面倒くさい」というのはそうなのだろう。F# の let mut と同じように、変更可能な変数の書き方のほうが *面倒くさい* ほうが、心理的に楽に流れる(これは五行大儀の前文にも掛かれている、というネタを披露しておく)ので行動経済学的にも理にかなっている。

よって、vue2 のコードでは、this.items や this.cur が頻発する。どうせローカルでしか参照しない方針ならば this は不要であろうという思想が vue3 にはある。

vue3 の script

では、同じコードを vue3 形式で書いてみよう

import { ref, onMounted } from 'vue'
import axios from 'axios'

/**
 * カテゴリのクラス
 */
class Category {
    id: number
    title: string
    category: string
    image: string
    created_at: string | null
    updated_at: string | null
    is_delete: boolean
}

// カテゴリ一覧
const items = ref([] as Category[])
// 選択したカテゴリ
const cur = ref({} as Category)
// 表示モード(列挙型)
enum MODE {
    DETAIL,     // 詳細
    NEW,        // 新規作成
    UPDATE,     // 更新
    DELETE,     // 削除
}
const mode = ref(MODE.DETAIL)



/**
 * カテゴリ一覧を取得する
 */
function getCategories() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        items.value = response.data
    })
}
/**
 * ひとつのカテゴリを取得する
 */
 function getCategoryById( id: number  ) {
    axios.get(`http://localhost:8000/api/category/${id}`)
    .then ( response => {
        console.log( response )
        cur.value = response.data
    })
}

/**
 * 新規作成のAPIを呼ぶ
 * @param item 更新する Category 
 */
function createCategory( item: Category ) {
    console.log( "called createCategory" )
    // TODO: POST を記述する
    return 
}

/**
 * 更新のAPIを呼ぶ
 * @param item 更新する Category 
 */
function updateCategory( item: Category ) {
    console.log( "called updateCategory" )
    // TODO: PUT を記述する
    return 
}


/**
 * 削除のAPIを呼ぶ
 * @param item 削除する Category 
 */
 function deleteCategory( item: Category ) {
    console.log( "called deleteCategory" )
    // TODO: DELETE を記述する
    return 
}

/**
 * ひとつのカテゴリを表示する
 * @param item 選択したカテゴリ
 */
function onClickItem( item : Category) {
    console.log( "onClickItem " + item.title )
    mode.value = MODE.DETAIL
    getCategoryById( item.id )
}

/**
 * 選択項目を新規作成する
 */
function onClickCreateItem() {
    console.log( "onClickCreateItem " + cur.value.title )
    mode.value = MODE.NEW
    cur.value.id = 0
    cur.value.title = ""
    cur.value.image = ""
}

/**
 * 登録ボタンを押下
 */
function onClickCommit() {
    console.log( "onClickCommit " + cur.value.title )
    console.log( "onClickCommit " + cur.value.image )
    if ( mode.value == MODE.NEW ) {
        // 新規作成の場合 POST を呼ぶ
        createCategory( cur.value )
    } else {
        // 更新の場合 PUT を呼ぶ
        updateCategory( cur.value )
    }
}

/**
 * クリアボタンを押下
 */
 function onClickClear() {
    console.log( "onClickClear ")
    if ( mode.value == MODE.NEW ) {
        // 新規作成の場合
        cur.value.title = ""
        cur.value.image = ""
    } else {
        const original = items.value.find( x => x.id == cur.value.id )
        if ( original ) {
            // 更新の場合
            cur.value.title = original.title
            cur.value.image = original.image
        }
    }
}

/**
 * 削除するボタンを押下
 */
function onClickDelete() {
    console.log( "onClickDelete ")
    // 削除 API を呼び出す
    deleteCategory( cur.value )
}

/**
 * 選択項目を更新する
 */
function onClickUpdateItem() {
    if ( !cur.value.id ) {
        console.log( "ERROR: onClickUpdateItem cur.value.id is null" )
        return 
    }
    console.log( "onClickUpdateItem " + cur.value.title )
    mode.value = MODE.UPDATE
    
}

/**
 * 選択項目を削除する
 */
function onClickDeleteItem() {
    if ( !cur.value.id ) {
        console.log( "ERROR: onClickDeleteItem cur.value.id is null" )
        return 
    }
    console.log( "onClickDeleteItem " + cur.value.title )
    mode.value = MODE.DELETE

    /*
    const result = confirm("削除してよろしいですか?")
    if ( result == true ) {
        deleteCategory( cur.value )
    }
    */
}

// ページ表示時に、カテゴリ一覧を取得する
onMounted(()=>{
    getCategories() 
})

コードは TypeScript なので、Category というクラスを定義しているのと、 MODE 列挙子を定義しているのと違いはあるが、同じ動作にしてある。

画面を表示するときのモードは リアクティブを使う

const mode = ref(MODE.DETAIL)

リアクティブという用語がでてくるが、実は vue2 ではうまく隠蔽化されていたものが、vue3 ではむき出しになってしまっただけである。理由は、むき出しにしたほうがステートありとステート無しの画面が意図的に区別ができて画面の表示スピードが上がる、のだが、vue2 ユーザーが vue3 に移行するときに躓くのはここが大きいだろう。

/**
 * クリアボタンを押下
 */
 function onClickClear() {
    console.log( "onClickClear ")
    if ( mode.value == MODE.NEW ) {
        // 新規作成の場合
        cur.value.title = ""
        cur.value.image = ""
    } else {
        const original = items.value.find( x => x.id == cur.value.id )
        if ( original ) {
            // 更新の場合
            cur.value.title = original.title
            cur.value.image = original.image
        }
    }
}

クリアボタンを押下したときに、編集画面を空白あるいは戻す処理を記述したものである。

画面のモードとして新規作成と更新がある訳だが、この部分は別々の関数にわけてもよい。実際に template 上では、新規作成では「クリア」、更新では「戻す」になっていて、ちょっと混乱した形になっている。これは新人研修用に少し混乱させることが目的でもある(実際の業務では、このようにモードで切り替えることが多いので)ので、これで良しとする。

vue2 では this.cur や this.items で参照されていたものが、vue3 では cur.value.* や items.value.* になっている。vlaue が付いているのはリアクティブ(変更通知が可能)となっているので、変更通知が必要のない固定値の場合には、cur.* や items.* と書くことができる。vue3 の本で、このあたりの違いが掛かれているのが無いのが残念なところなのだが、まあ、初学な人には「vue3 ではリアクティブ ref や reactive を使います!」としたほうが迷いが少ないので、そっちのほうがいいだろう。慣れてくるとスピードを優先させて、ref なしと ref ありを区別するとよい。

お次は、編集画面を vue コンポーネント化する

さて、いよいよ vue コンポーネントを使った形に書き換えていく。

vue2 で props と $emits を使って親子のコンポーネント間でデータをやり取りしたものを、vue3 ではどういう風に書くのかということになる。まだ Atomic Design のほうまで至らないが(私的には至らなくても良いと思っている、その理由は別途書こう)。MVVM パターンとか、Flux とかを混ぜると、その「危険度」がわかると思う。

カテゴリー: 開発 | Vue2 から Vue3 へ移行する ボタンイベント編 はコメントを受け付けていません

Vue2 から Vue3 へ移行するための其の壱

「其の壱」とは書いたものの、其の弐があるかわからないが、ひとまず Vue2 から Vue3 へ移行するときの肝を示しておく。

Vue2 から Vue3 の大きな変更点は、Options API から Composition API によって関数の使い方が変更になったということなのだが、あいにく Vue2 の経験の浅い私としては、どちらでも構わない。2年程前から Vue.js を教えていたときは Vue2 であって、去年の途中から Vue3 が盛り上がってきて、今年に至っては Vue3 を使わざるを得ない≒教えざるを得ない状況になっている。

新人にとっては、Vue2 だろうと、Vue3 だろうと関係ないのだが、いざ配属された後では既存の Vue2 を修正する機会が出てくるだろう。あるいは Vue2 から Vue3 に変更するしないといけないかもしれない。さらに言えば、現状では Vue3 の情報(特に日本語の情報)が少なく、インターネットで検索する限り Vue2 の情報がでてきてしまう。古めの Vue2 の情報に引っ掛からないようにするためには、Vue2 の書き方から Vue3 へ自らが書き換えられるようにならないといけない。

Vue3 の参考書

既に、Vue3 の参考書はいくつかでいるのだが、どうもしっくりこなかった。

やむなく、「基礎から学ぶ Vue.js」 https://www.amazon.co.jp/dp/B08JYTSY4T を使っていたのだが、つい5月に技術評論社から「Vue.jsポケットリファレンス」 https://www.amazon.co.jp/dp/B0C26WFHFX が出版されたので急遽これを使っている。

「~ポケットリファレンス」のほうは、Vue3.js を網羅している。今回は、通常のポケットリファレンスとは違って、テクニックの部分よりも基本的なところに紙面を割いている。この部分がちょうど新人研修に向いている。

移行する画面

テスト的に、現在新人研修で使おうとしている画面を Vue2 から Vue3 に移行してみよう。実際のところは、既に Vue3 で作った画面を Vue2 形式に移植しているので、Vue2 特有の関数の使い方になっていないかもしれないが、書き方のスタイルとしては、3年ほど前に私が業務で使った書き方に揃えてある。

画面にリストが表示されて、項目をクリックすると詳細が表示される。一覧や詳細はいちいち Web API を呼び出すようにしてある。

本来ならばコンポーネント化して props と $emit を使ったほうがいいのだが、ここでは Vue2 から Vue3 移行への雰囲気を味わうために省いている。後日、 props と $emit を使ったパターンはやってみようと思う。

template

実は Vue2 でも Vue3 でも template は全く同じものが使える。移行前の Vue2 でどれだけトリッキーなことをやっているかという度合いにもよるのだが、素直な画面であれば変更は必要はない。とはいえ、実際のところはアニメーションやキーイベントなどを細かい操作が入ってい来るから、Vue3 に変更になった時に変更されたイベントには対応しないといけないだろう。

<template>
    <div>
        <h1>Mos Main by vue2</h1>
        <div>
            <ul>
            <li v-for="it in items" :key="it.id">
                    {{ it.id }} : <a @click="onClickItem(it)">{{ it.title }}</a>
                </li> 
            </ul>
        </div>
        <hr />
        <div>
            <table class="table">
                <tr>
                    <td>id: </td><td>{{  cur.id }}</td>
                </tr>
                <tr>
                    <td>title: </td><td>{{  cur.title }}</td>
                </tr>
                <tr>
                    <td>image: </td><td>{{  cur.image }}</td>
                </tr>
                <tr>
                    <td>作成日時: </td><td>{{  cur.created_at }}</td>
                </tr>
                <tr>
                    <td>更新日時: </td><td>{{  cur.updated_at }}</td>
                </tr>
            </table>
        </div>
    </div>
</template>

template からは項目をクリックしたときに onClickItem メソッドを呼び出して、下のほうにある cur.id などが変更されるようにする。items がコレクションで、cur がカレントアイテムということになる。

Vue2 のコード

Vue2 で書くとこんな風になる。MosMain.vue というファイルの script となる。

import axios from 'axios'

export default {
  name: 'MosMain',
  props: {
  },
  data() {
    return {
        items: [],  // カテゴリ一覧
        cur: {},    // 選択したカテゴリ
    }
  },
  mounted() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        this.items = response.data
    })
  },
  methods: {
    getCategoryById( id ) 
    {
        axios.get(`http://localhost:8000/api/category/${id}`)
        .then ( response => {
            console.log( response )
            this.cur = response.data
        })
    },
    onClickItem( item )
    {
        console.log( "onClickItem " + item.title )
        this.getCategoryById( item.id )
    },  
  }
}
  • items や cur は、data の return にする
  • 画面を表示するときに mounted 中で記述する。リストを this.items にいれておく
  • template からのイベントを methods の中に記述する。methods の中の関数を呼び出すときは this を付ける
  • 全体を export default { … } で囲って、公開しておく。これを App.vue から使う。

書き方に慣れれば、 props, data, mounted, methods の中に順序よく関数やデータを書くことになる。算出プロパティ(computed)とか監視プロパティ(watch)も同じように分類される。

が、慣れればこの書き方でうまくいくのだが、最初のうちは無理矢理型にはめられている気分であまりよくない。後で解るのだが、それぞれのブロックは、vue.js からのフックするときの情報となっているので、Java の EJB みたいな感じになっている。

Vue3 のコード

この Vue2 のコードを Vue3 に書き換えてみる(実際は、Vue3 から Vue2 を書き起こしている)。

コード的には TypeScript で書いているので、型を指定するために Category クラスを作っているが、概ね Vue2 のコードと機能的には変わらない。

import { ref, onMounted } from 'vue'
import axios from 'axios'

/**
 * カテゴリのクラス
 */
class Category {
    id: number
    title: string
    category: string
    image: string
    created_at: string | null
    updated_at: string | null
    is_delete: boolean
}

// カテゴリ一覧
const items = ref([] as Category[])
// 選択したカテゴリ
const cur = ref({} as Category)


/**
 * カテゴリ一覧を取得する
 */
function getCategories() {
    axios.get('http://localhost:8000/api/category')
    .then ( response => {
        console.log( response )
        items.value = response.data
    })
}
/**
 * ひとつのカテゴリを取得する
 */
 function getCategoryById( id: number  ) {
    axios.get(`http://localhost:8000/api/category/${id}`)
    .then ( response => {
        console.log( response )
        cur.value = response.data
    })
}

/**
 * ひとつのカテゴリを表示する
 * @param item 選択したカテゴリ
 */
function onClickItem( item : Category) {
    console.log( "onClickItem " + item.title )
    getCategoryById( item.id )
}


// ページ表示時に、カテゴリ一覧を取得する
onMounted(()=>{
    getCategories() 
})
  • 画面へ表示する場合は、ref か reactive を使う
  • 呼び出す関数は function で記述しておく。
  • 画面から呼び出される onClickItem も function で記述しておく
  • 画面の初期表示では onMouted にラムダ式を渡す

Vue2 の書き方で items, cur のところが、items.value, cur.value を使うところが一番分かりづらいと思う。リアクティブという仕組みを使って画面に値変更を通知するのだが、実は Vue2 では items, cur が仮想 DOM を使って通知されていたものが、Vue3 になってリアクティブという形でむき出しになってしまった形のため、こうなっている。

リアクティブ自体は、C# では MVVM パターン、Rx という形で10年以上前から使われているものなので、C# や Java 界隈では知っていれば知っているというパターンなんだけど、Vue3 で出て来たので混乱するというパターンだろう。ちなみに「~ポケットリファレンス」では、MVVM パターンと一緒に解説が付けられている。

見た目上、items.value のほうに真の値が入っていて、items には getter/setter で来るんである、と考えればよい。ちょうど算出/監視プロパティの両方が同時に入っているイメージだ。

何故、Vue3 にこのリアクティブの部分がむき出しになったのかと考えるに、Fultter の Stateありと Stateless のコンポーネントにちなんでではないかと思う。Fullter の場合、画面内の変更がない場合は Stateless で作ることが多い。画面内で変更がないので表示が早いためだ。この部分を「関数型の画面表示」ということもあるが、つまりは、State(状態)を持つと仮想 DOM の扱いで負担が大きいのだろう。なので、画像や説明を表示するだけのコンポーネントであれば、ref や reactive を使わずに、状態変更なしで軽い形で表示できる(のではないか?)のが Vue3 の特徴と類推される。

あと、Vue 3.0 の頃では setup に色々と詰め込まなければ駄目だったが(これはかなり非道い仕様だと思う)、Vue 3.1 からは scrupt setup を使うことで、上記のような素直なコードが書ける。というか、setup の中身がそのまま書かれただけなのだが、読みやすのは確かである。

Vue2 の mounted のブロックが、Vue3 では onMounted となっている。算出/監視プロパティの書き方も同じようにラムダ式を渡す形になる。つまり、Vue2 はあらかじめ mounted というブロックに強制的に書かなければいけなかったが、Vue3 ではコードを書く側で onMounted を呼び出して能動的に登録することができる。データ設定の向きの違いであるけれども、私としては Vue3 のほうが、縛られない感じがして気分がいい。まあ、気分の問題だけど。

関数の部分だが、次のようにラムダ式を使っても書ける。

const onClickItem = ( item : Category ) => {
    console.log( "onClickItem " + item.title )
    getCategoryById( item.id )
}

私としては、変数や定数は const、関数は function にしたほうが検索性が高いと思うのだが、このように const で揃えてある実例も多い。「~ポケットリファレンス」この const で統一されている。

Vue2 と Vue3 のコーディングスタイルの違い

Vue2 のがちがちにフォーマットされたスタイルから、Vue3 のちょっと自由に書けるスタイルになったわけだが、C# のコンポーネント(クラス)の場合には、MVVM パターンとイベント登録をすればよいわけで、Vue3 の書き方がのほうが、素直にクラス設計が出来そうだがどうだろうか?ルールの部分が少なくて済むという利点がある。

参考先

Vue3の衰退を招いたのはとCompositionAPIかもしれない という考察 – Qiita https://qiita.com/fruitriin/items/81691ce68cf3678f3bda

カテゴリー: 開発 | Vue2 から Vue3 へ移行するための其の壱 はコメントを受け付けていません

Good Code 書籍の例外処理とフェールセーフ思想の違い

新人教育に「Good Code ~」を使うのだけど、第4章の「エラー」の部分だけは、ちょっと私の思想とは異なる。一般的に言えばそういう書き方が推奨されるのだが、実務的にはフェールセーフな動きが求められることが多い。ゆえに、ある程度のコードを書かずに「Good Code ~」を先に読んでしまうと変なところに陥る(たびたび原著が抜けてている点も含めて)の難点・・・なのだけど、まあ、新人教育には便利な教材である。

例外を呼び出し元に通知するべきか?

15年前位に、アプリケーション例外とシステム例外の区別をすべきかどうか?というのが流行った時期がある。システム例外はオペレーションシステムが発生する例外、アプリケーション例外はシステム以外全般のユーザーが使うという意味でのアプリケーション層ということになる。

一般的にユーザーが使うアプリケーションは、何か問題が起こったときにはユーザーに対して「予期せぬエラーが起こりました!」でアプリケーションが落ちる。「予期せぬエラー」というのは、アプリケーションにとって予期せぬエラーであって、回復不能なのでエラーダイアログには「OK」ボタンしかない。ユーザーには、何らかのエラー番号が伝えられることもあれば伝えられないこともない。予期しないのは、ユーザーにとって予期しない動作だったかもしれないが、アプリケーションを作った開発者にとっても予期しないエラーだったのかもしれない。ともかく、エラーメッセージを出して落ちる。

この「予期せぬエラー」は、たいていの場合はシステム例外が起因となる。アプリケーション内のロジック的なエラーではなく、システム側でHDDが書き込めなくなった、LANケーブルが抜けた、CPUが熱暴走した、という際のハードエラーも含め、オペレーションシステム開発者が出す例外も含める。よくあるのは、システム側がヌルポインターを返す場合もこれにあたる。これの場合、アプリケーションが対処できるかといえば、できる場合もあるしできない場合もある。回復可能かと言えば、再試行できる場合もあれば(Wi-Fiの接続がわるいとか、輻輳とか)、回復できない場合もある。そんなアプリケーション開発者としてはレアなケースでもあり、ユーザーにとってもレアなケースである。

一方で、アプリケーション例外は、アプリケーションの中で何らかのロジック的なエラーが発生した場合と言える。なので、完全なエラーのないプログラムであれば、アプリケーション例外は一切発生しない!のかもしれないし、別の意味での「例外」を発生させる場合もできる。「Good Code ~」の中でいればユーザーが入力する電話番号にアルファベットが紛れ込んだときの場合にもアプリケーション例外は使える。

歴史的な視点でいえば、結果的にアプリケーション例外とシステム例外はあまり区別しなくてよいという結論に至っている。アプリケーション例外、システム例外の2点で考えるとエンドポイントがクライアントとサーバーの2つのように見えるのだが、オペレーションシステムだとしてもはハードウェアに対してはクライアントになるし、アプリケーション内でもライブラリとユーザーインターフェースの区切りがあれば、ライブラリはシステムサイドと言える。なので中間層があるわけで、決定的な区別はない。特に、Java や C# のように VM を使う場合は、VM 内部での発生がシステム例外なのかアプリケーション例外なのか分離しづらいところもあるので、まあ、雰囲気的に例外を扱うという着地点に至っている。

そんな中で、システム例外をアプリケーションの表層(ユーザーインターフェース)に押し出すのがよいのか、という疑問がでてきる。「Good Code ~」で扱う Java の場合、呼び出す関数に throws を付けて「無視してはいけない例外」を明記することができるのだが、現在、他のプログラム言語で流行らなかったところを見ると例外の情報は呼び出し元にとってあまり重要ではないということらしい。端的に言えば、システム内部で発生した例外を細かく呼び出し元に通知していると、実に膨大な例外の数が渡ってくるために現実的に処理しきれなるためだろう。

そう、Google が開発した Go には例外がない。まあ、そういうことだ。

なので、なんらかの例外を呼び出し元に通知する場合は、

  • 有限である例外の種類

がひとつの基準になるだろう。例外の種類が5種類ぐらいならばいいけど、100種類ぐらい飛んでくるのであれば、それは何かクラス構造とかそもそも例外を飛ばす必要があるのか?というのを考え直した方がいい。この視点は必要になる。

例外通知はプログラムのバグ修正のためにあるのか?

これは C++ で例外処理が入った頃から思っていたことなのだけど、例外の通知は、誰のためにあるのだろうか?「Good Code ~」や当時の上司には「例外を隠蔽してしまうと、コードのバグが隠蔽されてしまう」ので、例外を隠さないに呼び出し先の関数では例外を呼び出し元に通知するという方針をとることが多い。

が、そもそも、実運用で動いているコードに対して「バグが隠蔽されるから」という理由で例外処理を潜ませておいて、例外が発生したらアプリが停止する(バグの特定のために?)というのはユーザーにとっては変な話ではないだろうか?

なので、私の方針としては、

  • 関数内で例外が発生したときは、エラーログを出力し、デフォルト値を返す(知らん顔をする)
  • ユーザー入力やデータ値でエラーになったときは、デベロッパー環境以外では異常値が入力されたときと同じように、通常のエラーメッセージと同様にユーザーに通知する(ユーザーの異常値なのかシステムの異常なのかは判断がつかない)

というコードにするのがベターと考える。もちろん、システムの方針によっては例外を発生してアプリケーションを停止させることもあるのだが、たいていの場合は停止させない。というか停止できない場合が多い。

  • 組み込みシステムの場合は、内部エラーが発生してもリセットする「人間」が不在のことが多いので、ひとまず動作を続けるパターンが多い。
  • 基幹システムの場合、止めることができない場合が多いので、勝手にアプリケーションを落とさない。応答はするものの、エラーメッセージを大量に吐き出すという対処で、デベロッパーに通知を送る。あるいは異常な状態に陥っても、メールを送る、通知を送る等の手段を残す。
  • ロボット系の緊急停止は、動作プログラムは別につくらないといけない。異常が発生した場合は、アームの位置を危険ではない状態に戻したのちに、停止するというスタイルになる。
  • もし、原子力関係のソフトウェアでバグが発生した場合、緊急停止するのではなく、現状維持がベターである。本当の緊急停止は監視者が手動で行えるので、ソフトウェアは閾値の加減となる。

このように製造業のソフトウェアシステムの場合は、プログラムのバグの追求よりも、安全にシステムが停止するという意味での「例外処理」が優先される。例外に対処するのは、利用者を守るために安全に停止する(あるいは動作を継続する)のが目的のなる。これは「フェールセーフ」の考え方になる。

xUnit 以前ならば、バグが隠蔽されるという意味もわからなくもないが、実運用しているソフトウェアに対して開発者の利点のためだけに「例外」を使うのはどうかと思われる。利用者の利点を最大限にするなれば、

  • プログラム内の回復不可能な異常発生時には、ユーザーに通知をして判断を仰ぐ。
  • プログラム内の回復可能な異常発生時には、エラーログを出力しユーザーには通知しない。

という使い分けが必要と思われる。「Good Code ~」では、後者の部分が省かれてしまっている問題がある。

bool login( string username, string password ) {
	
	if ( username == null ) return false;
	if ( password == null ) return false;

	// ログインチェック
	...
}

たとえば、login という関数で、username と password が渡されて bool を返すとする。

このとき、何らかのプログラムミスで、username や password に null が入っていてた場合は、例外を発生してプログラムの開発者に「login 関数の呼び出し元が異常なことをやった!」と通知したほうがいいだろうか?

私はそうは思わない。プログラムのコードのミス(それが login 関数を作った開発者ではなく、login 関数を呼び出す別の開発者であったとしても)を運用時にユーザーに通知するのは不親切であろう。もともと、login 関数の目的はユーザー名とパスワードのチェックなのだから、ユーザー名が null の人はいないので、しれっと false を返して、ログインできないようにすればいいだけだ。呼び出し元がバグのままかどうかなんてユーザーには知ったことではない。login 関数は例外を発生するのではなく、エラーメッセージを吐く位がせいぜいだろう。あるいは、C言語の assert 関数のように、リリースビルドをするときには影響しないものを考えるべきだろう。login 関数には bool 値(ログインできるかできないか?)の判断を任せているだけで、ユーザー名が null かどうかは尋ねていないのだから。

カテゴリー: 開発 | Good Code 書籍の例外処理とフェールセーフ思想の違い はコメントを受け付けていません

chu言語の配列とクラス

配列

💋言語の配列は

let x = [1,2,3,4,5]

のようにカンマで区切ることができないので、

👼🍎👈👫1😀2😀3😀4😀5👫

のようにブロック👫と区切り文字😀を使う。

クラスか構造体

いまのところ Rust の構造体に近い形で、データのみ扱う

🐱🍱🍊🌸🍱

この意味は

struct cat {
var orange
var cherry_blossom
}

のように cat クラスで、orange と cherry_blossom というプロパティを持っているこという意味である。オレンジとサクラは変数名(プロパティ名)なので、多分、型が「くだもの」と「花」なんだと思う。「猫」と「オレンジ」と「サクラ」は、ユーザーが自由に決めていい変数(ただし絵文字1文字に限る)。クラスや構造体は🍱で区切る。

型をどう指定しようか?string, number, boolean 程度が指定されてあれば十分だと思う。また、JavaScriptのように型なしでもよい。

struct cat {
var orange : string
var cherry_blossom : number
}

これを

🐱🍱🍊🖖🎻🌸🖖🎵🍱

に変換する。明示的に型を指定したいときは、🖖で区切る。そして型として🎻の string や、🎵の number を使う

カテゴリー: 開発, chu | chu言語の配列とクラス はコメントを受け付けていません

chu 言語のパースと改行

💋言語は、1文字でひとつの単語をあらわす。構文はF#に寄せる。ということで、

👼🍎👈10

let x = 10 ;

となる。

中間言語としては

👼🍎👈10

これを

:let: “apple” “=” “10”

に直したうえで

let apple = 10

にすれば簡単でよい。

F#の構文と1対1になるとは限らないので

🤔🍎🤝10🙆OK🙅NG

の場合は

if apple = 10 then OK else NG

になるが、

“if” “apple” “=” “10” “then” “OK” “else” “NG”

から直すことになる。この場合は、then と else がはいっているからうまい例ではないが。

あとで変更

文の扱い

💋言語では、単語の区切りに空白を用いない。できれば、改行も使わないようにしたいののだが、あまりいい思いつきがないので、

👼🍎👈10
👼🍊👈20

という2つの文を1行に書きたいときは

👼🍎👈10😀👼🍊👈20😀

のようにする。ちょうどC言語の「;」と同じ働きをする。

😀😀😀😀😀

は空行を示している。

ブロックの開始と終了

F#の場合はブロックの開始と終了をインデントで表すが、💋言語にはインデントというかそもそもタブとか空白とかいう概念がないので、なんらかのブロック(スコープ)が必要になる。

👫👼🍎🤝10😀👼🍊🤝20👫

こんな感じに👫 で囲うのがいいのだが、どうせならば対になるようにしたい。sh の if と fi みたいに男女を入れかえることは可能なのか?

カテゴリー: 開発, chu | chu 言語のパースと改行 はコメントを受け付けていません

不具合票の書き方

おそらく、急遽新人に不具合票の書き方を伝えないといけない気がするのでメモ書き。

もともと新人教育では一連のソフトウェア開発を教えるので、PMBOKに従い、

・要件定義
・設計(外部設計、内部設計、詳細設計、画面設計など)
・コーディング(単体テスト込み)
・結合テスト(不具合票の取り回し込み)
・システムテスト、運用テスト
・運用保守に関する手順書

あたりを網羅するようにしている。なにか、品質が悪くてどうしようもなくなっている場合は、初手として上記の PMBOK のプロセスが欠けていないか確認する。各プロセス自体は必須という訳ではないが、視点として落としてしまうとそこにハマるという特性がある。なので、俯瞰して網羅的に知っておいて損はない。知っているが、時間や予算の関係で「やらない」という選択肢を敢えてとるし、あるいは簡略化・省力化する。あるいは、うまくいかないときは原点に返って、組み込むことを考える。まあ、それが PMBOK のナレッジな処な訳だし。

不具合票の取り回し

不具合票は主に「結合試験」のような、複数名が関わっているときに必須となる。

・テスター
・コードを書いた人あるいは直す人
・設計書を書いた人あるいは仕様を解説できる人

3つの役割(設計→コーディング→テスト)はひとりで担ってもいいし、設計とコーディングがひとりでもいいのだが、そこそこの規模になるとこのプロセスは3分割されて少なくとも3名以上が関わってくる。

不具合であれ設計であれ、3者の中で情報をとりまわすのだから、なんらかのコミュニケーションが必要となる。伝わるならばメールでもいいし、口頭でもいいし、文書でもいい。

しかし、3者が同じ時間帯で働いているとは限らないし、不具合の直しがシーケンシャルになるとは限らない。むしろ、テストや直しは同時並行的に行われる。なので、不具合の情報をいったん取り回しができるように(居酒屋の伝票のようなものだ)しておくのが必要ある。

不具合票には、

・何か起こったか?
・いつ起こったか?
・再現するにはどうしたらいいか?
・どういう風に直せばよいのか?

が明確になるように記述する。

なので、不具合票には、

・テストをした人の名前(コードを直すときに現象を聞く相手)
・テストをしたときの日時(コードのバージョンが必要)
・何をしたら、どうなって、こうなったのか(再現性の手順、あるいは再現しにくいことを示す)
・現状と期待値を示す(現状の誤っている状態、そして期待する状態を明記する)

があればよい。これに加えて

・テスト仕様書の番号(あれば)
・不具合票の番号(問い合わせのときに便利なので)
・不具合票のタイトル(問い合わせのときに便利なので)

というものがあればよい。

最近は修正したコードを Git などで管理することが多いので、

・不具合票番号あるいは試験番号
・コードに書かれている試験番号、あるいはコミットするときの番号

を一致させておくと検索しやすくなる。

不具合票のフォーマット

平易なテキストでも構わないし、適当なスプレッドシートを使ってもよい。ただし、Excel の狭いセルだけで取りまわそうとすると、画面キャプチャや手順が書きづらくなるのでやめておいたほうがいい。現在では GitHub の isseus の活用が一番楽だが、まあ、そのほかのバグトラッカーをつかってもよい。

  • 不具合票の番号
  • 対応する試験の番号
  • 不具合票のタイトル
  • 試験をした人の名前
  • 試験をした日時
  • 試験対象のコード名(バージョンとか)
  • 不具合の現象(動かないことを具体的に記述)
  • 不具合の再現手順(箇条書きで記述)
  • 期待される動き(試験項目へのリンクでもok)
  • 合否判定
  • 不具合を対処する人(だれが直すのかわからなくなるので。一般にコードを書いた開発者だが、テスターから開発者が見えない状況があるので注意が必要)

これに加えて

  • コードを修正した人
  • コードを修正した日時
  • 再試験をした人(たいていは試験をした人)
  • 再試験をした日時

を不具合票を直すときに記録をすればよい。

このあたりの手順は、車の修理とかパソコンの修理票とかを真似すればよい。

不具合票をカウントする

いわゆる、コードに中に含まれている不具合は複数の不具合票としてあらわれるかもしれない。また、対象の不具合を直さないと他の試験が進まないかもしれない。

この現象は、ワインバーグの言う「ブロッキング」に他ならない。

なので、不具合票がたくさん出たからといって品質が悪いとは限らない。不具合票に対処すればソフトウェアの品質は上がるわけだし、不具合票が0件だとしたら、潜在的な不具合があって見つからないのかもともとコードの品質がよかったのか(仕様や設計との齟齬も含めて)実はわからない。極端な話をすれば、テストを全くしなければ、不具合票は0件だし、不具合も出ない(見えないだけなのだが)のだ。

昔は、不具合票の数を指標として扱っていたものだが、「不具合票が多い」≠「コードの品質が悪い」とは限らないの注意が必要である。不具合票が残ったままであれば、コードの品質が悪いのは確かなことなのだが。

そういえば、富士通 Japan の時間 ID の件は全社展開(というか協力会社全社展開?)になったっぽいのだがどうなっただろうか?

何の目的でテストをするのか?

基本、テストプロセスはその前工程にあるプロセス(要求、設計、コーディング)をチェックするために行う訳で、対応としては

  • 要件定義(Rquirement)に対する、システムテスト/運用テスト
  • 設計(Design)に対する、結合テスト
  • コーディング(Coding)に対する単体テスト

に分かれる。

一般的に再帰テストが行われるのが、コーディングと単体テストのワンセットで、これが XP のプラクティスのひとつになる。ただし、昨今はモックアップなどをつくることによって、設計をチェックするための結合テストも自動化を行うことも可能となっている。

他にユーザービリティテストやセキュリティテストがあるが、これが別のものと考えてよい。要件定義におて機能外要件としてユーザビリティ(Web APIの応答が何秒以内とか)を求めることもあれば、漠然と画面の色やパーツの配置を求める場合ので、それぞれのプロジェクトによる。

要件定義書が顧客の求める要件であり、設計書は要件定義をシステム的に満たすための設計(建築でいうところの設計図)になるので、視点の向きがことなる。

このため、所謂「不具合票」が目的とするところは、

  • 要件を満たしているかを、システム的にチェックする
    → 要件が満たされない場合は、要件を満たせない設計になっている可能性が高いので、設計書を見直す
  • 設計を満たしたコーディングがされているか(詳細設計を含む)チェックする
    → 設計通りにできていない場合は、設計通りに直す
    → 設計通りに作ったとしても、「設計通りにはコーディングできない状況」、「設計が要件を満たしていない」場合もあるので注意が必要。
  • コーディング自体のミスなので、コードを直せば不具合とはならない

という3つの分野に分かれる。

要件を満たすかどうかのシステム試験の場合、ウォーターフォール開発の場合は、この時点で不具合が発生してもなんともならないことが多い。そうならないように、適宜スパイラル開発か、アジャイル開発か、マイルストーンを使ったウォーターフォール開発に直す。ちなみに、富士通 Japan の ID 重複の件は、この部分に属する。要件を満たさない設計を作ってしまったか、要件そのものに「時刻を ID とする」と書かれていたか。肥大化する要件定義書を抱えている場合は、後者となることが多い。特に F の場合は。

自動化する単体試験においては、XP のプラクティスのようにテスト駆動をしてもよいし、コーディングをしながらテストをしてもよい。昔のように、コードをビルドしてリリースしなければ画面が動作しないということはないので、画面を動かしながら Web API の動作チェックも可能である。かつ、そのように設計とコーディングの順序を決めておけば、不要な不具合票の取り回しをしなくてもよい。労力が減る。Vue.js とかで画面を作っている場合は、常にエラーが出ない状態でコードを組む方がよい。かつ、その時々で画面の動作を画面設計書などと照らし合わせながらチェックをすれば、過大なテスト項目をこなさなくても済む。

システム試験と単体試験の間にある結合試験では、当然のことながら「単体試験である程度の品質を確保しておくこと」が重要になる。いわゆる、コードの品質が悪いと、結合試験の進捗度合いにもかかわるし、そこでブロッキングされてしまう。つまりは、ここに「Good Code ~」における「コードの品質」が関わってくる。コードが高品質か低品質かの前に、

  • 結合試験をスムースに行えるだけのコードの品質が保たれているか?

という具体的な品質基準がここに出てくる。結合試験がスムースというのは、不具合がゼロというわけではない、設計ミスもあれば設計の読み違いもあるので不具合がゼロにはならない。ただし、そもそもコードが動いていないとか、コードを修正しようとしたときに誰かひとりに頼らないといけない(コメントがない、コードが読みづらい、コード分割され過ぎているなどなど)状況に陥るときは、明らかに「コードの品質が悪い」と言ってよいだろう。

というわけで、3つのテストプロセスにおいては、何の目的でテストを行っているのか?の違いを明確にしたうえで進めるのがよい。

カテゴリー: 開発 | 不具合票の書き方 はコメントを受け付けていません

絵文字プログラム言語 chu 構想はじめ

実は構想だけは10年前ぐらいからあるのだけど(言語名自体は一発ネタだし)、今朝方、円城塔「文字渦」を読んで絵文字≒表意文字1文字という縛りでこだわってみてもいいのでは?と思って思考実験してみる。

もともとの発想として関数言語を絵文字であらわすというのある。

let x = 10 ;

これを

😇🍎👈10

のようにする。

print x ;

💋🍎

のように変換する。

if x = 10 then "ok" else "ng"

🤔🍎🤝10🙆ok🙅ng

になる。

単語や数字は面倒なので、英数字をそのまま使って、文法はすべて絵文字の1文字であらわす。1文字しかないので、単語の区切りとかで半角スペースはいらない。print あるいは console.log が💋なのは、chu 言語だから。

プログラム言語の意味論を考え直す

絵文字1文字縛りにするのは、構文解析が楽というのもあるけど、制御文なので使われる「if」や「for」などの単語は、ひとつの単語がひとつの意味を成している(この場合は何らかの制御をするという意味で)と考えられる。例外としては、for … in のように2つの単語でひとつの制御を示すこともあるが「繰り返し処理」という制御の拡張(inが補助的なもの)と考えることもできる。

となれば、ひとつの制御そのものは、ひとつの単語すなわちひとつの表意文字=絵文字に対応させることが可能で、いわゆる予約語といわれるものは、すべてひとつの絵文字に置き換えられるのではないか?というのがある。

さらに、変数とは何を指している(特に変更できない immutable な変数)というかというと、文字列や数字、長い文章などのひとつのまとまりを、ひとつの単語(変数)としてあらわしていることになる。これは、構文解析の制限にもよるのだろうが、変数はひとつの単語としてあらわすようになっている。例えば「hello」という変数は作れるが「hello world」という空白を含んだ変数を作ることはできず「hello_world」のようにアンダーバーをつけることでひとつの単語にする。例外としては「’hello world’」のようにシングルクォートなどを使いひとまとまりにすることも可能ではあるのだが、これはシングルクォートに囲まれた範囲が、ひとつの単語に相当する、と考えられる。

また、ラムダ式を変数に保存するとき(C# だけどこんな感じ)

let func = () => {} ;

これは func という変数に、ラムダ式を割り当てている。つまりラムダ式(あるいは関数、あるいは一連の処理)をひとつの単語に集約されているといえる。たとえば

let greet = () => { return "Good morning" }

このようにすれば、greet() によって、Good morning という文字列が返される、あるいは一連の処理を記述できる。これは、一連の処理を関数名に集約している「構造化」という仕組みになる。そう、プログラムの構造化というものは、複数の処理をひとつの単語に集約させるという意味論がある。

さらに拡張すれば、オブジェクト指向のクラスやインスタンス(オブジェクト)も、複数の変数や処理群をひとつの単語にまとめるという意味論がある。

となれば、あらゆるプログラムは、無限に絵文字があるとすれば、ひとつの単語に集約できるのではないか?という思考実験が可能になる。まあ、実際、ターミナルの「コマンド」がそれを担っているわけで、できるわけですが。絵文字を無限に作ることは可能だけど、無限の絵文字を覚えることは不可能なので、「実現可能ではありますが現実では不可能ですね」というやつです。

シェークスピアの「ハムレット」の長い文章は「ハムレット」というタイトルを表す、ひとつの絵文字に集約できるだろう。

ということです :)

カテゴリー: 開発, chu | 絵文字プログラム言語 chu 構想はじめ はコメントを受け付けていません

新人教育のための Good Code, Bad code のメモ書き

一読してみて、これならば新人研修に使えるかもしれないと思いつつ、同時に研修に使うときには「元ネタ」を明確にしないとミスリードっぽくなるだろう。と思われたので、そのメモ書き。

参考文献のほうに

  • リファクタリング
  • デザインパターン
  • Unit Test

が書いてあるので、XP のプラクティスに合わせてもいいと思われる。

コードの品質とは何か?

日本語でいうとこの「品質とは」に関係してくるので、「高品質なコード」と「低品質なコード」の比較や、「1.2 コードの品質にゴール」に書かれている4つの箇条書きのように漠然としてくると、新人(特にコードを書く前の新人)にはちょっと困った自体に陥るんじゃないかな、と思う。

ここでいう「正しく動くコード」というのは、動くべき仕様書に対応するコード、という隠れた意味がある。細かく言えば、動くべきことを記述した「仕様書」と、実際に顧客が想定している「動作」とは乖離があるわけで(あの例の顧客が求めていたものの図)漠然と、「正しく動くコード」あるいは「正しく動作し続けるコード」が先にでてくると、混乱するのではないか?

たとえば、建築で言えば、「正しく動くコード」というのは、家の屋根に相当する。

雨が降っても部屋が濡れないように屋根を付ける、という要望を出したとしよう。屋根があればいいので、実は柱が1本でもいい。傘のようなものでもいい。雨の量はここでは問わないので小雨をしのげるだけでもいいかもしれないし、台風に耐えるような屋根にする必要もあるかもしれない。

だから「雨に濡れないように屋根をつける」ようにコードを書くこともできる。エラー処理をつけないようなコードや、例外を受け付けないようなコード。ある程度の範囲は許容するけど、極値では落ちてしまうかもしれない。でも99%のデータは、中央値にまとまっているから、たいていの場合は「正しく動くコード」として扱うことができる。たまに、極値が来ると正しく動かないコードに陥ってしまう。

だから「正しく動く」の部分は範囲が広い。もう少し修飾するならば、「おおむね正しく意図的に動くコード」というべきだろうか。

「おおむね」というのは、画面からの入力はおおむね大丈夫な感じとか、マイナンバーカードを入れたときに氏名に外字が使われていない限りは大丈夫とか、半角カナが入ってない場合は大丈夫とか、そんな感じで、「おおむね」正しく動いている状態。

プログラマから言えば、この「おおむね」正しく動いている状態というのは、到底「正しく動く」の範疇に入らないように見えるのだけど、少なくとも、想定している数字やひらがなを入れたときにデータベースにきちんと保存されているね、ぐらいの気持ち、いわゆる「正常系」がきっちりと動いているのを「正しく動くコード」と緩い形で決めてしまってもいい気がする。

でも、プログラムというものは正常系だけで済むものではない。コードのほとんどは異常系や例外をうまく処理するためのものが多い。その部分が「正しく動き続けるコード」にあたる、と思われる。

正常系でうごいていても、何かの異常系で動かないことがある。これは正しいコードと言えるか問えわれれば、言える。だって、正常系が動いていて、顧客の要望もおおむね動いているわけだから、入力するときにちょっと注意を払えばだいたい正常に動くわけだから「正しく動くコード」といってもいいだろう。しかし、「正しく動き続けるコード」とは言えない。

そう、雨をしのぐための傘のような屋根を付けて、正しく雨がしのげる住宅、と言っていいようなきもするし駄目のような気もするのだが、少なくとも「雨をしのぎ続ける」ことはできない。そういう意味では、ちょっとした小雨のような雨ならばしのげる正常系かもしないがい、横から吹き込むような雨には無力な屋根だ。少なくとも壁をつけないと雨をしのぐという住宅の意味をなさない。これが「正しく雨をしのぎつづける」住宅と言えるだろう。

だから「正しく動くコード」でキーポイントになるのは、顧客の要求を正常に処理ができるか、緩い基準になる。コードという視点から言うと発散してしまいそうだが、仕様書とか要件定義という意味においては、まず第一に正常系の要求をきちんと処理できること、ひとまず例外などを除いた機能要件が満たされることが正しく動くコードというよりも「正しく動くソフトウェア」あたると思う。

ただし、このままだと品質工学的な意味での「品質」に達しない。低品質ということになる。

では、正しく動き続けるコードあるいはソフトウェアは何に気を付ければいいのか?ということになる。少なくとも、ちょっとした例外処理(外字を使うとか、半角カナとか)は対処しないと品質が高いとは言えないだろう。それは、ある程度の例外を許容して正常に動くというものがある。「ある程度の例外」というのがキーポイントになると思われる。

カテゴリー: 開発 | 新人教育のための Good Code, Bad code のメモ書き はコメントを受け付けていません

EntityFramework Core 7.0 を使ったときにサーバー証明書エラーが発生する場合の対処

.NET 6 で利用していた EF Core 6.0 を .NET 7 の EF Core 7.0 にアップデートしたときに「信頼されていない機関によって証明書チェーンが発行されました」のエラーが出てきて SQL Server に接続できないことがあります。これは、開発用に立てたローカルの SQL Server に接続するときに、サーバーの証明書が正しくない(自己証明書を含む)ときに発生するようです。正しくは SQL Server が動いているサーバー証明書をローカルPCにインストールするか、正しいサーバー証明書をいれればいいのですが、本運用ならともかくとして、開発用サーバーに正式な証明書をいれることはありません。

EF Core 6.0 ではサーバー証明書を無視するらしいのですが、EF Core 7.0 できっちりとチェックするようになったための問題のようです。

開発中の場合は、サーバーにある自己証明書(開発機で動いていれば自分自身のPC)をスルーするようにします。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    base.OnConfiguring(optionsBuilder);
    if (optionsBuilder.IsConfigured == false )
    {
        var builder = new SqlConnectionStringBuilder();
        builder.DataSource = "(local)";
        builder.InitialCatalog = "mvcdb";
        builder.IntegratedSecurity = true;
        builder.TrustServerCertificate= true; // サーバー証明書を常に信頼する
        optionsBuilder.UseSqlServer(builder.ConnectionString);
    }
}

DbContext クラスで、TrustServerCertificate の値を true にしておくと接続できるようになります。

config の場合は「TrustServerCertificate=Yes」を追加しておきます。

参考先

ssl – Entity Framework Core 7 connection certificate trust exception – Stack Overflow https://stackoverflow.com/questions/74467642/entity-framework-core-7-connection-certificate-trust-exception

カテゴリー: 開発 | EntityFramework Core 7.0 を使ったときにサーバー証明書エラーが発生する場合の対処 はコメントを受け付けていません

週40時間勤務と20%ルールでプロジェクト納期を厳守する

まだ会社にいたときに「なぜ、週40時間にこだわるのか?」と何度か上司に聞かれたことがあります。そのころはXP(エクストリーム・プログラミング)が始まったころで、当時はそのプラクティスのひとつとして「週40時間勤務」が重要だったのと、来日したケント・ベック氏に勢いで「ケント・ベック氏自身は勤務時間40時間を守れているのでしょうか?」と質問したときに、「文章書きのときは守れていない。けど、プログラミングをするときには週40時間が必須!」と強調されたから、という理由があったわけですが、実はもうちょっと別な根拠があります。

その後、日本のIT業界は少し不況に入ったので、仕事が減り少しずつ勤務時間も改善されてきたので、週の勤務時間にこだわる必要もなくなったのですが、たまに「炎上プロジェクト」をみるので、勤務時間の話を書き残しておきます。

正確な事前見積もりをあきらめる

大抵のプロジェクトで「炎上プロジェクト」(あるいはデスマーチプロジェクト)となるのは、事前のスケジュールミスが問題です。昨今の炎上プロジェクトはITプロジェクト自体が短期化しているところもあって、短い時間の中に開発を詰め込みすぎ、人数が足りなくて「炎上」ということが多いです。炎上とは言え、プロジェクトは完了します。たまに、大規模プロジェクト(IBMの特許プロジェクトのような)で炎上後にプロジェクトが崩壊するパターンもありますが、数か月から半年以内のWebプロジェクトやシステム開発のようなものは、炎上しつつも、なんとか納品するという(スケジュールは伸びるかもしれませんが)という結末を得ることが多いです。

半年以下のプロジェクトの場合、週で言えば、24週間になります。契約がうまく移動できるスクラムや顧客を含めたチケット駆動で行っている場合は、金額やスケジュールの調節でうまくできるかもしれませんが、たいていのプロジェクトでは、事前に金額やスケジュール(納品日)が決まっていることが多いです。

納品日がずれると、検収日がずれるので会社としては集金日がずれます。と同時に、伸びた分だけ社員に給与を払うことになります(派遣ならば、派遣先に延長を申し込むことになる)。この増加分や遅延分を顧客が支払ってくればよいのですが、そうもいきません。

その昔、ファンクションポイント法(FP法)などで見積もりの正確さを求めいた時期があるのですが、最近のアジャイル開発を含めて計画駆動でさえ、正確な事前の見積もりはうまくいきません。本来ならば概要設計まで行って機能別に見積もりをしたいところですが、事前の要件定義すらぐらぐらなところがあるので、そのぐらぐらの事前条件に使って概要設計をして精密な見積もりを出しても意味はありません。

アジャイル開発においては正確な事前見積もりを清く諦めます。諦めて、プロジェクトが進む中で機能の増減やスケジュールの遅延などを調節していきます。が、果たして、日本のIT業界の現状としてはそれはできないことが多いのです。

概算であれ事前の見積もりが求められ、ざっくりとした見積金額がそのまま正式な金額になりがちで、さらにサービスリリース日は顧客の営業の都合で決められることが多く、開発側の都合だけで金額も開発スケジュールも決められるものではありません。

プロジェクト予算とスケジュールを固定化する

以前にも何度か話していますが、プロジェクトの予算については二重帳簿方式を使います。二重帳簿というと聞こえは悪いですが、まあ、仕方がないです。

  • 顧客に提示するプロジェクト予算
  • 社内で開発するときのプロジェクト予算と実績

顧客や営業先に用意する「プロジェクト予算」は、見積もりを提示した突端に固定化されます。割引交渉で減額されることはあっても、増額されることはまずありえません。機能はそのままで、割引を要求されることも普通にあります。これは、営業の値段交渉のうちなので、実装するときの機能とは切り離しておくとよいです。

つまり、切り離したところに、実績としてのプロジェクトの内部予算があります。実際に社員や派遣さんなどが働いた工数を積んで、具体的に払う金額つまりプロジェクトで利用した金額を積み重ねていきます。

同時に、顧客に提示するスケジュールも提示した途端の固定化されます。納品日あるいはリリース日が決定されると、プロジェクト開発において前に倒すことはあっても後ろに倒れることはあり得ません。ごめんなさいするしかない場合は、後ろに倒れますが。

ここで「プロジェクトバッファ」を取って、プロジェクトのスケジュールを立てたいところですが、もう少し泥臭い方法があります。プロジェクトのバッファの方式は、バッファ自体を食いつぶさないの注意しないといけません。これは、チケット駆動やスクラムとの併用も可能(PMBOKにも指針があります)なのですが、常に「学生症候群」(夏休みの宿題を後ろに残すあれ)を気にかけてチームマネジメントをしないといけないので、途中で機能が増減する場合は、なかなかハンドリングが大変です。

プロジェクト計画時に月160hで計算する

小規模のプロジェクト(5,6名程度)で期間が半年以内のプロジェクトに限定するものですが、プロジェクトメンバの勤務時間を柔軟にすること(残業も含めてという意味で)を前提にして、プロジェクトのスケジュールを立てます。

このとき、週40時間、月160時間で見積もりをします。稼働日数を22日にしたり、忙しい月だからといって稼働時間自体を月200時間で計算してはいけません。あくまで、週40時間、月160時間で統一的に計算してしまうのがミソです。

プロジェクトバッファ(「保険」とも言う)の仕組みは簡単です。

  • 通常は、週5日で8時間勤務で行う
  • 少し忙しいときは、週5日で10時間勤務(2時間残業)で行う
  • 炎上間近のときは、週6日で10時間勤務、土曜出勤日曜日は休みで行う

こうすうことによって、週40時間が、少し忙しめのときは2割増し、炎上ぎりぎりのときは1.5倍のときの開発時間を確保できます。休日を外さないのは、継続的に1か月以上働けるのはこれが限界だからです。その後に燃え尽きてしまいますからね。

このように、月160時間でプロジェクトの期間見積をしておくと、機能追加や見込み違いなどでも同じメンバーで1.5倍までは開発期間を確保できます。

逆に言えば、1.5倍を超える場合は、人員を確保しないと駄目≒予算が増える、ということです。本来ならば残業代が増えるのですが、それは会社によりけりなので。

また、あらかじめ Google の 20% ルールを採用しておいて、繁忙期には20%ルールを外してもよいというルールにしておきます。フリーランスでだらだらとやっている身ですが、本の執筆や開発が混ざったときはこのパターンを採用していますね。お試しあれ。

カテゴリー: 開発 | 週40時間勤務と20%ルールでプロジェクト納期を厳守する はコメントを受け付けていません