クラス利用することの利点について考察

こんにちは。エンジニアのS.Sです。

今回は、Google Apps Script(GAS)を使って、クラスを記述していきたいと思います。
なぜ、GASを利用したのかというと、無料でかつ環境構築を行わずJavaScriptベース(ES6)の開発が出来ると考えたからです。クラスは、主に大規模開発を行う際に使用される場合が多いかと思いますが、小規模開発でも積極的に使用することでメリットが出てくると考え、記事を書きました。

今回はGASを使用して、なぜクラスを用いて記述するのか、クラスを用いる際に注意するべき点、最後にはコードを【資産】にするための条件を考えていきたいと思います。


GASとは何か


Google Apps Script(GAS)は開発環境の設定が非常に簡単で、手軽に利用できるのが大きな利点です。GASの特徴として以下の点が挙げられます。

1.ブラウザベースの開発
GASのコードはウェブブラウザ上で直接編集できるので、Googleアカウントを作成すれば、特別な開発環境を設定する必要がなく、どこからでもアクセスしてコードを書くことができます。しかし、ブラウザベースの開発のため、ローカル環境では実行することが出来ません。

2.Googleサービスとの統合
GASは、Gmail、Googleドライブ、スプレッドシート、ドキュメントなど、他のGoogleサービスと簡単に統合できます。これにより、これらのサービスを拡張したり、様々なサービスの自動化が可能になり、カスタムのワークフローを作成したりすることが容易になります。

3.サーバーレスで簡単なデプロイと共有が可能
GASはサーバー設定する必要がなく、Googleのクラウド上で実行されます。これにより、インフラストラクチャの管理や設定に関する心配が不要になります。また、外部サーバやクラウドにデプロイする必要がなくなり、すぐにウェブアプリケーションとして実運用が可能になります。さらに、作成したスクリプトは簡単に他のユーザーと共有したりすることができます。

4.学習コストの低さ
JavaScriptベースであるため、既にJavaScriptに精通している開発者にとっては、学習コストが低く、すぐに利用を開始することができます。

これらの特徴により、GASは特に小規模なプロジェクトや、迅速なプロトタイピング、Googleサービスを拡張するためのスクリプト作成に最適なプラットフォームと言えます。また、ノンプログラマーや初心者にとっても利用しやすい環境を提供しています。

なぜクラスで記述するのか


1.コードを整理しやすくする

クラスを使うことで、関連する機能を一つの単位にまとめることができます。これにより、コードが読みやすく、管理しやすくなります。

ここでは、「Book」クラスを作成し、それを使って本の情報を管理する例を考えてみます。クラスには、本のタイトル、著者、ISBN番号などのプロパティと、それらの情報を出力するメソッドを含めます。

class Book {
  constructor(title, author, isbn) {
    this.title = title;
    this.author = author;
    this.isbn = isbn;
  }

  // 本の情報を表示するメソッド
  displayInfo() {
    return `Title: ${this.title}, Author: ${this.author}, ISBN: ${this.isbn}`;
  }
}

// クラスの使用例
function main() {
  // 'Book' クラスのインスタンスを作成
  const myBook = new Book('Book名', '著者名', '978-1234567890');

  // 本の情報を表示
  Logger.log(myBook.displayInfo());
}

このコードでは以下のことが行われています。

  • Book クラスが定義されています。コンストラクタで本のタイトル、著者、ISBNを受け取り、それらをプロパティとして保持してます。

  • displayInfo メソッドは、その本の情報を文字列で返します。

  • main 関数では、Book クラスのインスタンスを作成し、その displayInfo メソッドを呼び出しています。

このようにクラスを使用することで、データと機能(メソッド)を一つの単位(クラス)にまとめることで、コードが整理され、読みやすくなります。また、新しい本の情報を追加したい場合には、新しい Book クラスのインスタンスを作成するだけです。

2.再利用性の向上

クラスを定義することで、そのクラスのインスタンスを複数作成し、異なるコンテキストで再利用することができます。これは、特に大規模開発や、コードをモジュール化して再利用したい場合に有効です。
 先ほどの「Book」クラスを使い、インスタンスを複製して、再利用をしてみます。今回は「myBook2」というインスタンスを作成し、本の情報を表示させます。

class Book {
  constructor(title, author, isbn) {
    this.title = title;
    this.author = author;
    this.isbn = isbn;
  }

  // 本の情報を表示するメソッド
  displayInfo() {
    return `Title: ${this.title}, Author: ${this.author}, ISBN: ${this.isbn}`;
  }
}

// クラスの使用例
function main() {
  // 'Book' クラスのインスタンスを作成
  const myBook = new Book('Book名', '著者名', '978-1234567890');
  // '新たなインスタンスを作成
  const myBook2 = new Book('Book名2', '著者名2', '123-1234567890');

  // 本の情報を表示
  Logger.log(myBook.displayInfo());
  Logger.log(myBook2.displayInfo());
}

このコードでは以下のことが行われています。

  • main 関数で、Bookクラスのもう一つのインスタンス(myBook2)を作成し、それぞれの displayInfo メソッドを呼び出しています。

このようにクラスを使用することで、一つの定義から複数のインスタンスを生成し、異なるデータで同じ構造を再利用することができます。このアプローチは、コードの重複を減らし、大規模開発やモジュール化が必要な場合に特に有効です。

3.カプセル化

クラスを使うと、データ(プロパティ)とそのデータを操作する関数(メソッド)を一緒に保持できます。これにより、オブジェクト指向プログラミング(以下、OOP)の原則であるカプセル化を実現し、データの整合性を保ちやすくなります。

class Book {
  constructor(title, author, isbn) {
    this._title = title;
    this._author = author;
    this._isbn = isbn;
  }

  // タイトルのゲッター
  get title() {
    return this._title;
  }

  // タイトルのセッター
  set title(newTitle) {
    this._title = newTitle;
  }

  // 作者のゲッター
  get author() {
    return this._author;
  }

  // 作者のセッター
  set author(newAuthor) {
    this._author = newAuthor;
  }

  // ISBNのゲッター
  get isbn() {
    return this._isbn;
  }

  // ISBNのセッター
  set isbn(newIsbn) {
    this._isbn = newIsbn;
  }

  // 本の情報を表示するメソッド
  displayInfo() {
    return `Title: ${this._title}, Author: ${this._author}, ISBN: ${this._isbn}`;
  }
}

// クラスの使用例
function main() {
  const myBook = new Book('Book名, '著者名', '978-1234567890');

  // 本の情報を表示
  Logger.log(myBook.displayInfo());

  // タイトルを変更
  myBook.title = 'ああああああああああ';
  Logger.log(myBook.displayInfo());
}

このコードでは、プロパティをプライベートに設定し、外部から直接アクセスできないように変更します。その代わり、ゲッター(読み取り専用のメソッド)とセッター(書き込み専用のメソッド)を提供して、データの安全なアクセスを可能にします。

プロパティ title, author, isbn がプライベート(_title, _author, _isbn という名前で宣言)となり、外部から直接アクセスすることはできません。このようにデータをカプセル化することで、オブジェクトの状態を外部から不適切に変更されることを防ぎ、データの整合性を保つことができます。

また、main 関数では、新しいセッターメソッドを使用して、インスタンスのタイトルを安全に変更しています。これにより、クラスの外部からでも、プロパティにアクセスして変更することができますが、そのアクセスはクラスが提供するメソッドを通じて行われ、データの不正な変更や不整合を防ぐことができます。

4.継承と拡張性

クラス構文では継承が可能です。これにより、既存のクラスのプロパティやメソッドを新しいクラスで再利用し、拡張することができます。これは、コードの重複を減らし、拡張性を高めるのに役立ちます。

継承を利用して、「Book」クラスから新しいクラスを作成し、機能を拡張する例を示します。ここでは、「Book」クラスを基にして、電子書籍に特化した「EBook」クラスを作成してみます。この「EBook」クラスは「Book」のすべてのプロパティとメソッドを継承し、加えて電子書籍特有のプロパティやメソッドを持つことになります。

「EBook」クラスを作成します。(「Book」クラスは、【3.カプセル化】のクラスを使用。)

class EBook extends Book {
  constructor(title, author, isbn, digitalFormat) {
    super(title, author, isbn);
    this._digitalFormat = digitalFormat;
  }

  get digitalFormat() {
    return this._digitalFormat;
  }

  set digitalFormat(newFormat) {
    this._digitalFormat = newFormat;
  }

  displayInfo() {
    return `${super.displayInfo()}, Digital Format: ${this._digitalFormat}`;
  }
}

// クラスの使用例
function main() {
  const myEBook = new EBook('Book名', '著者名', '978-1234567890', 'ePub');

  // 電子書籍の情報を表示
  Logger.log(myEBook.displayInfo());
}

このコードでは以下のことが行われています。

  • EBook クラスは Book クラスを継承しています(extends キーワードを使用)。

  • コンストラクタで super() メソッドを使用して、親クラスのコンストラクタを呼び出し、タイトル、著者、ISBNを設定しています。

  • 電子書籍特有のプロパティ digitalFormat(電子書籍のフォーマット)を追加しています。

  • displayInfo メソッドをオーバーライドして、電子書籍のフォーマット情報も表示するようにしています。

このようにして、「Book」クラスの機能を「EBook」クラスが継承し、新しい機能を追加して拡張しています。これはコードの重複を避け、拡張性を高めるのに役立ちます。

5.明示的な構造

クラスはオブジェクトの構造を明示的に定義します。これにより、他の開発者がコードを理解しやすくなり、チームでの開発が効率的になります。

明示的な構造を持つ「Book」クラスを使って、他の開発者が理解しやすいコード例を示します。ここでは、新しい機能として、複数の本を管理する「Library」クラスを作成します。この「Library」クラスは「Book」オブジェクトのコレクションを管理し、特定の機能を提供します。

「Library」クラスを作成します。(「Book」クラスは、【3.カプセル化】のクラスを使用。)

//配列を追加
class Library {
  constructor() {
    this.books = [];
  }

  // 本を追加するメソッド
  addBook(book) {
    this.books.push(book);
  }

  // すべての本の情報を表示するメソッド
  displayAllBooks() {
    this.books.forEach(book => {
      Logger.log(book.displayInfo());
    });
  }
}

// クラスの使用例
function main() {
  const library = new Library();
  const book1 = new Book('Book名', '著者名', '978-1234567890');
  const book2 = new Book('Book名2', '著者名2', '978-1234567890');

  // 図書館に本を追加
  library.addBook(book1);
  library.addBook(book2);

  // 図書館の全ての本の情報を表示
  library.displayAllBooks();
}

この例では、以下のことが行われています。

  • Library クラスは、複数の Book オブジェクトを格納する books 配列を持っています。

  • addBook メソッドを使用して、新しい Book オブジェクトを books 配列に追加します。

  • displayAllBooks メソッドは、配列内のすべての Book オブジェクトの情報を表示します。

このようにクラスを設計することで、オブジェクトの構造と役割が明確になり、他の開発者がコードを理解しやすくなります。また、この構造はチームでの共同作業を効率化するのにも役立ちます。

6.ES6のサポート

Google Apps Scriptは最新のJavaScript(ES6)をサポートしており、クラス構文が導入されているため、最新のJavaScriptの機能を最大限に活用できます。
加えて、ES6にはクラス構文の導入のほかにも、アロー関数、テンプレートリテラル、デストラクチャリング代入など、多くの便利な機能が含まれています。
先ほど作成した「Book」クラスを用いて、これらのES6の機能を活用した例を示します。ここでは、アロー関数とテンプレートリテラルを使用して、コードをより簡潔に記述します。

class Book {
  constructor(title, author, isbn) {
    this.title = title;
    this.author = author;
    this.isbn = isbn;
  }

  displayInfo = () => `Title: ${this.title}, Author: ${this.author}, ISBN: ${this.isbn}`;
}

// テンプレートリテラルとアロー関数を使った別の例
const displayBookInfo = (book) => {
  const { title, author, isbn } = book;
  return `Title: ${title}, Author: ${author}, ISBN: ${isbn}`;
}

// クラスの使用例
function main() {
  const myBook = new Book('Book名', '著者名', '978-1234567890');

  // メソッドを使用して本の情報を表示
  Logger.log(myBook.displayInfo());

  // 関数を使用して本の情報を表示
  Logger.log(displayBookInfo(myBook));
}

このコードでは以下のES6の機能を使用しています。

  • アロー関数: displayInfo メソッドと displayBookInfo 関数はアロー関数を使用して定義されています。アロー関数は、読みやすい構文を提供してくれます。

  • テンプレートリテラル: 文字列の結合にはテンプレートリテラルを使用しています。これにより、変数の埋め込みが簡単になり、コードの可読性が向上します。

  • デストラクチャリング代入: displayBookInfo 関数内で、book オブジェクトからプロパティを抽出する際にデストラクチャリング代入を使用しています。

これらの機能は、ES6の導入によってGoogle Apps Scriptで利用可能になったものであり、より現代的で効率的なコードを書くことを可能にしています。

これらの理由から、Google Apps Scriptでクラス構文を使用すると、より効率的で、再利用可能で、拡張しやすいコードを書くことが可能になります。また、チームでの開発や、大きなプロジェクトでの作業がより容易になります。

注意するべき点


・カプセル化について

今回は「3.カプセル化」において、本のタイトルを外部から変更しましたが、カプセル化の原則においては、オブジェクトの内部状態(プロパティ)は外部から直接変更を行うのは原則に従うとあまりよい方法ではないと思います。やはり、データの整合性とセキュリティの観点からみても行わない方がよいと思います。
セッターメソッド自体、カプセル化の原則に反するわけではありませんが、セッターを用いる場合には以下のような点を考慮することが重要です。

  1. データの検証と整合性の保持:セッターメソッドを使うときは、入力されるデータがオブジェクトの状態として適切かどうかを検証することが重要です。不適切なデータがオブジェクトの状態として設定されることを防ぐためです。

  2. 不要なセッターの排除:すべてのプロパティにセッターを提供する必要はありません。例えば、一度設定されたら変更されるべきではないプロパティ(例:ISBN番号)はセッターを提供しないことで、そのプロパティの不変性を保持することができます。

  3. オブジェクトの責任と範囲を明確にする:オブジェクトがどのような責任を持ち、どのような操作を許可するかを明確にし、それに基づいてセッターメソッドを設計することが重要です。

カプセル化はオブジェクトの内部状態を適切に保護するために重要ですが、これにはセッターメソッドを適切に使用することも含まれます。セッターメソッドは、必要に応じて、かつ慎重に設計されるべきです。

・継承について

継承は非常に強力な機能であり、適切に使用すればコードの再利用性と整理性を大いに高めることができます。しかし、継承を過度に使用すると、いくつかの問題が生じる可能性があります。

  1. 複雑性の増加: 複数のレベルにわたる継承は、コードの追跡と理解を難しくします。特に、複数の親クラスから継承される特性が多い場合、どの機能がどのクラスから来ているのかを理解するのが難しくなります。そのため、もし、継承を行いたいとしたら、親クラスから2-3階層までを基準として、継承を行った方がよいと考えています。

  2. 密結合: 子クラスは親クラスと密接に結びついています。これにより、親クラスの変更が子クラスに大きな影響を与える可能性があります。このような密結合は、ソフトウェアの保守性と拡張性に悪影響を及ぼすことがあります。

  3. 再利用性の低下: 特定の機能に特化しすぎた継承は、他の機能での再利用を難しくする可能性があります。クラスが多くの機能を持ちすぎると、必要ない機能も一緒に引き継がれ、無駄が生じることがあります。

  4. 壊れやすい親クラスの問題: 親クラスに加えた変更が、予期せず多くの子クラスに影響を及ぼすことがあります。これは、親クラスがどのように使われているかを正確に理解せずに変更を加えた場合に特に発生しやすいです。

これらの理由から、継承を使う場合には慎重に設計することが重要です。一般的には、「コンポジション(組み合わせ)優先の原則」が推奨されます。これは、可能な限り継承よりもオブジェクトの組み合わせを利用するというもので、より柔軟で再利用しやすい設計を実現するのに役立ちます。

コードを【資産】にするために


コード量

  • 手続き型と比べて増加する可能性: オブジェクト指向はモジュール性や再利用性を重視するため、初期のコード量が多くなることがあります。しかし、長期的にはメンテナンスや機能拡張が容易になるため、全体の労力は削減される可能性があります。

学習コスト

  • ノンプログラマーには高いハードル: 特にOOPの慣れていないユーザーにとって、クラス構文や継承などの概念は理解が難しい場合があります。

  • チームでの共通理解が必要: チーム内での共有知識や共通のコーディングを周知・理解させることが重要になっていきます。

しかし、学習コストについてはチーム内で教育していくことで、以下のように長期的な利点を考えることができます。

1. より効率的なコード管理と品質向上

  • 再利用性の向上: OOPはコードのモジュール化を促進し、再利用しやすいコードを作成することを可能にします。これにより、長期的には開発時間と労力が節約されます。

  • メンテナンスの容易さ: クラスベースの設計は、コードの保守とデバッグを容易にし、長期的なプロジェクトのメンテナンスコストを削減します。

  • バグの減少: カプセル化はバグの発生を減少させることに貢献し、ソフトウェアの信頼性を高めます。

2. 長期的な柔軟性と拡張性

  • スケーラビリティ: オブジェクト指向設計はプロジェクトの成長に合わせて拡張しやすい構造を提供します。

  • 拡張性: 変更要求や新しい機能追加に対して、OOPは適応しやすい柔軟な設計を可能にします。

3. チームワークの強化とスキル向上

  • 共通の理解: OOPの原則に精通したチームは、コードベースに対する共通の理解が可能になり、メンバー間で共通のコミュニケーションができます。

  • プロフェッショナルな開発スキル: OOPの学習は、現代の多くのプログラミング言語とフレームワークはオブジェクト指向の原則に基づいているため、プログラマーの適応能力が向上し、スキルセットを広げ、キャリア機会を増やせる可能性もありえます。

コードを資産とする条件

  1. リーダブルコード:保守性と可読性を重視することで、時間が経過しても理解しやすいコードを書くことができます。

    • リーダブルなコードとは、他の開発者が容易に理解できるように書かれたコードを意味します。このコードは、保守性が高く、将来の変更や拡張が容易になるよう設計されています。

    • 変更容易性: リーダブルなコードは、変更が必要な時に容易に修正できることが重要です。プログラムの一部を変更する際に、他の部分への影響が最小限であることが望ましいです。

    • バグの特定: コードを明確に書けば、バグを特定しやすくなります。これにより、問題解決の時間が短縮され、プロジェクトの効率が向上します。

    • 構造の明確性: コードは論理的かつ整理された構造である必要があります。適切なインデントやコメントの使用、関数と変数の明確な命名が必要になります。(上記のコードでも書いてしまっていますが、【同じような変数名にしない】ように。。。)

    • 簡潔さ: 不必要な複雑さを避け、可能な限りシンプルに保つことが重要です。簡潔なコードは理解しやすく、エラーが発生しにくいです。

    • コメントとドキュメント: コードには、目的や複雑なロジックが簡潔に説明されたコメントを含めるべきです。また、ドキュメントは、コードの使用方法や設計の意図を明確にするために重要です。

  2. 再利用性が高いコード: 再利用性が高いコードとは、様々なプロジェクトや環境において、容易に再利用できるように設計されたコードです。このようなコードは、開発時間の短縮、コスト削減、一貫性の維持など、多くの長期的な利益を提供します。

    • モジュール性: コードはモジュール化されていることが重要です。独立したモジュールや関数は、特定の機能を実行し、他の部分と独立しているため、再利用が容易です。

    • パラメータ化: 柔軟性を持たせるために、コードはパラメータ化されるべきです。これにより、異なる条件や設定で同じコードを再利用できます。

    • プラットフォーム独立性: コードは、特定のプラットフォームやシステムに依存しないように書かれるべきです。これにより、異なる環境での利用が可能になります。

    • 外部依存性の最小化: 外部ライブラリやフレームワークへの依存を最小限に抑えることで、コードの可搬性が向上します。

    • 明確なドキュメント: 再利用可能なコードは、その使用方法と機能が明確に文書化されている必要があります。これにより、他の開発者がコードを理解しやすくなります。

    • 標準化されたコーディング規約: 一貫したコーディングスタイルと規約を使用することで、コードの読みやすさと理解の容易さが向上します。

  3. 拡張性が高いコード: 拡張性が高いコードとは、将来の要件の変更や機能の追加に対して、最小限の労力で対応できるよう設計されたコードを指します。このようなコードは、長期的に見て、開発の効率性とコスト効率を大幅に向上させます。

    • モジュール化: コードは、独立したモジュールやコンポーネントで構成されるべきです。これにより、個々の部分を独立して更新または置換でき、全体のシステムに影響を与えることなく拡張が可能になります。

    • 疎結合性: コードの各部分は、他の部分との依存関係を最小限に抑えるべきです。結合性が低い設計により、一部を変更しても他の部分に影響が及ばず、容易に拡張やメンテナンスが行えます。

    • 未来のシナリオを考慮: コードを書く際には、将来的なシナリオや拡張の可能性を考慮に入れることが重要です。これにより、将来の要件の変更に対して柔軟に対応できるようになります。

    • 再利用可能なコンポーネント: 拡張性の高いコードは、再利用可能なコンポーネントで構成されることが望ましいです。これにより、新しいプロジェクトや機能にこれらのコンポーネントを容易に統合できます。

    • 汎用的な設計: 汎用的な設計により、コードは様々な用途や環境に適応できるようになります。

  4. チームワーク・スキル向上:共通理解をすることで、メンバー内でのコミュニケーションや他技術の理解が容易になります。

結論


オブジェクト指向は、資産価値のあるコードを書くための強力なツールとして、正しく使用された場合、OOPは再利用性、拡張性、可読性を高め、長期的な価値のあるコードを生み出すことができます。
しかし、技術を最大限に生かすためには、バランスが重要でオブジェクト指向の利点を最大限に生かしつつ、過度な複雑さや過剰な設計を避けるバランスが重要です。
また、プロジェクトのニーズに合わせたアプローチの採用を行う必要があります。プロジェクトの規模やチームのスキルレベル、開発の目的に応じて、オブジェクト指向を取り入れる度合いを調整することが重要です。

個人的には、小規模開発であったとしても、オブジェクト指向開発を行うことで、大規模開発を行う際の基礎知識として役立つと思うので、積極的に利用していきたいと思います。

【参考文献】


内山 俊郎『わかりやすい情報システムの設計[第2版]』
川場 隆『新わかりやすいJava オブジェクト指向徹底解説 第2版』


エンジニアファーストの会社 株式会社CRE-CO S.S



この記事が気に入ったらサポートをしてみませんか?