大規模アプリ

人型の置物とノートパソコン

まだ勉強を始めたばかりなので、記事にする題材がありません。一旦自分の仕事を振り返ってみようと思います。

ずっと一人でソフト(アプリ)を作ってきたため、チームで作成するアプリの規模感が良くわかりません。プロフィールや経歴書に「中・大規模データベースソフトを構築しました」などと書くのですが、本当に大規模?という疑問が湧いてきましたので検証(言い訳?)したいと思います。

そもそも大規模アプリとは何行(何ステップ)以上なのか? 検索してもはっきりとした定義が出てきません。よく企業の会計システムは100万行をこえるとか、大手銀行のシステムは1000万行以上あるとか、Windowsのコードが1億行をこえるとかは誰が聞いても大規模と思うでしょう。Googleのコードに至っては20億行をこえるそうです。行数もすごいですが、開発費用も想像できません。

それでは視点を現実的なレベルに落として考えてみます。
IPA(独立行政法人 情報処理推進機構)によると、大規模システムとは100名以上のプロジェクト構成要員が携わるプロジェクトと定義されている様です。

規模目安
小規模システム30名未満
中規模システム30名以上~100名未満
大規模システム100名以上

この定義のままでは一人で開発していると永遠に大規模にならないので、人月の最小単位を1か月として100人月と定義をすり替えます。100人月のアプリを一人で開発した場合は8年と4か月程度かかる計算になります。8年という期間は現実的ではないので、一人が1か月に書くことができるコード行数を考えてみます。

使用する言語にもよると思われますが、JavaやC#等の高級言語を使用した場合は1か月におよそ1000~2000行程度のコードを作成できるといわれている様です。

以上から
100人 × 2000行 = 200000(20万行)
となり、20万行をこえると大規模アプリと言えなくもないかもしれません。(実際に100人も必要なプロジェクトが1か月で終わるとは思えませんが...)

Visual Studio Codeエディタ用のプラグインに「VSCode Counter」というものがあります。これは指定フォルダ以下に含まれている色々なソースコードを集計してくれます。実際にやって見たところ以下の様な結果になりました。

ソースコード行数の画像

これにはテストケースや開発環境が自動で生成したコードも含まれていますので、それらのコードを考慮すると何とか20万行をこえている感じでしょうか。また、アプリの規模が大きくなると一般的に保守性も低下しますので、Visual Studioでコードメトリックス(保守容易性指数)といわれる指標も測定してみました。(一部画像をぼかしてあります。)

保守容易性の画像

保守容易性指数は0~100まであり、100に近いほうが良いとされています。すべて緑になっていますが、これは20以下になるまで色が変わらないので保守しやすいかどうかは、実際にコードを見てみないとわかりません。色が変わっている、特に赤(指数が0~9)になっている場合は、他の人がメンテナンスするのはほぼ不可能に近い状態になっているので、早急なリファクタリングが必要です。60以上あればなんとか保守できるレベルの様です。

余談ですが、これらのアプリはすべてVisual Basic (VB.NET) で書かれています。C#という選択もありますが、お客様がご自身でメンテナンスされたいというご要望であったため、VB.NETを選択しました。

この様なアプリを複数制作しましたので、合わせ技で大規模ということでご容赦ください。

※(2025/06/19 追記)

単独で開発する場合は、開発コストが安く済むというのが最大のメリットと思われますが、評価(レビュー)する人がいないため独善的で属人的なコードになりがちです。そこでChatGPTにコードを評価してもらいました。(このコードだけでは評価できないので他のコードも追加しました。また少しでも評価が高くなるように質問の仕方を工夫したのは秘密です。)

Private Shared Iterator Function BlockXmlReader(uri As String, elemName As String) As IEnumerable(Of XmlElement)

    Try
        Dim settings As New XmlReaderSettings()
        Dim context As New Stack(Of XElement)

        Using fs As New FileStream(uri, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)

            Using reader As XmlReader = XmlReader.Create(fs, settings)

                reader.MoveToContent()

                While reader.Read()

                    Select Case reader.NodeType

                        Case XmlNodeType.Element

                            Dim xelem As New XElement(reader.Name)

                            If reader.Name = elemName Then
                                context.Clear()
                                context.Push(xelem)
                            Else
                                If context.Count = 0 Then Continue While
                                context.Peek.Add(xelem)
                                If Not reader.IsEmptyElement Then context.Push(xelem)
                            End If

                            If reader.HasAttributes Then

                                For i = 0 To reader.AttributeCount - 1
                                    reader.MoveToAttribute(i)
                                    Dim name = reader.Name
                                    Dim value = reader.Value
                                    Try
                                        If Not name.Contains(":") Then xelem.SetAttributeValue(name, value)
                                    Catch ex As Exception
                                        My.Logger.WriteException(ex)
                                    End Try
                                Next

                                reader.MoveToElement()

                            End If

                        Case XmlNodeType.EndElement

                            If reader.Name = elemName Then
                                If context.Count = 0 Then Yield Nothing
                                Dim a = context.ToArray
                                context.Clear()
                                Yield a.Last.ToXmlElement
                            Else
                                If context.Count = 0 Then Continue While
                                While context.Count > 0
                                    Dim elem = context.Pop()
                                    If elem.Name = reader.Name Then Exit While
                                End While
                            End If

                    End Select

                End While

            End Using

        End Using

    Catch ex As Exception
        My.Logger.WriteException(ex)
    End Try

End Function

結果は以下の通りになりました。

評価基準(100点満点中)

観点配点評価点(目安)コメント
1. 大きなXMLファイルからの指定エレメント抽出性能2016XmlReader + XElementで必要箇所のみ読み込みは良いがXElementの構築やToXmlElement変換のコストが懸念。メモリ効率や速度改善の余地あり。
2. 同名エレメント複数対応1010スタック管理で複数対応し、yieldで列挙しているため問題なし。
3. 呼び出し元のスレッド対応108非同期・並列を想定しFileShare.ReadWriteでファイルロック回避は良いが、例外処理のロギングがスレッドセーフか不明。
4. 名前空間非対応・コロンを含む属性排除109属性名に「:」を含むものを排除しているため要件対応済み。
5. XmlElement変換(GetAttribute使用目的)107XElement → XmlElement変換のため新規XmlDocumentを都度作成するのはコスト高い。キャッシュや他手法検討余地あり。
6. ファイル書き込み中の読み込み108FileShare.ReadWriteと既存チェックを前提としている点は良いが、ファイル更新中の不整合対応は曖昧。
7. 処理中断禁止・再読込可能性109例外キャッチで処理停止せず、yield返しも良い。再読込でDB更新可の設計に適合。
8. ログ書き込みとWindowsイベントログ記録109ログは例外捕捉ごとに呼び出されている。SyncLockで排他しておりスレッドセーフだが、例外内部ロギングがやや冗長。
9. パトライト連携・ユーザー通知55GUIスレッド呼び出し考慮とパトライト制御ありで実装良好。
10. コードの可読性・メンテナンス性54コメント・変数名は適切。Try-Catch多用は例外隠蔽やパフォーマンス懸念。

総合点: 85 / 100

おおむね良好な様ですが、以下の様な懸念もありました。

詳細コメント

  • パフォーマンス面
    XmlReaderはストリーム処理に優れるが、XElementの生成やToXmlElementのXmlDocument読み込みは重い。巨大XMLで多数呼び出しなら負荷増大。
    ToXmlElementは毎回新規XmlDocument生成するためコスト高く、改善余地あり(例えばXmlReader直結やXPath利用など)。
  • スレッド安全性
    ログの書き込みはSyncLockで排他制御されているのは良い。例外捕捉のロギングも一応実装されているが、複数スレッドの例外が重なるとログの順序性や競合に注意。
  • 例外処理
    Try-Catch多用は例外抑止になり、バグ発見が難しくなる恐れあり。重要部分は例外を上位に投げる設計検討が望ましい。
  • ファイルアクセス
    FileShare.ReadWrite指定で他プロセスの書込み中も読み込み可能。ただし中途半端に書き込まれたXMLの不整合処理やリトライタイミング制御は別途必要。
  • 要件への適合
    名前空間排除や複数同名エレメント対応、イベントログ連携、パトライト通知など要件は概ね満たしている。
  • 改善提案
    • ToXmlElement処理の高速化(例えばXmlDocumentを使わずにXmlReaderのまま処理するなど)
    • 例外ハンドリング戦略の見直し(例外は可能な限り上位へ投げるか、より詳細ログ出力)
    • ログの非同期書き込み検討でパフォーマンス向上
    • XML不整合検知・リトライロジックの明示的実装

パフォーマンスの改善余地がかなりある様です。

以上です。

Node.js

次の記事

Web Developer Bootcamp