私はCで小さなペットプログラムに取り組んでいます。そこでは、一連の正方形で構成されるゲームボードを持っています。
typedef struct _square {
bool checked;
} Square;
typedef struct _board {
Square *squares;
uint8_t width;
uint8_t height;
} Board;
これらの構造体を公開したくないので、代わりに、正方形がチェックされているかどうかをチェックする、square_is_checked
という単純な関数を作成します。戻り値の型がbool
であることは理にかなっています(ここではC99を想定しています)。
bool square_is_checked(Board *b, uint8_t row, uint8_t col)
{
return b->squares[(uint16_t)row * b->height + col].checked;
}
ただし、NULLポインター、または存在しない行と列が引数として渡される可能性があります。だから私はいくつかの簡単なチェックを追加したいと思います:
bool square_is_checked(Board *b, uint8_t row, uint8_t col)
{
if (!b) { return ???; }
if (row >= b->height || col >= b->width) { return ???; }
return b->squares[(uint16_t)row * b->height + col].checked;
}
ただし、bool
はtrue
またはfalse
のどちらかであり、エラー管理の余地はありません。ここでベストプラクティスと見なされるものは何ですか(たとえば、bool
へのポインターを渡すためにこの関数を書き直す必要がありますか)?
これは、実際にはCの関数からbool
を返す方法に関するものではありません。コードはすでにそれを実行しています。
問題は、実際には、可能な値のセットがbool
およびtrue
より大きい場合の正しいデータ型はfalse
であるということです。それは長い哲学的な議論の対象となる可能性がありますが、これは個人的なプロジェクトであるため、より大きな文脈での決定について心配する必要はありません。チームでは、既存のコードがこのような状況についてすでに意見を持っている可能性があるため、決定が下されることがあります。
したがって、この場合、考えられる答えはいくつかあります。いくつかのオプション:
bool
のままにし、ヌルポインターや無効な値(row > height
など)などの入力エラーにはfalse
を使用しますCHECKED
、UNCHECKED
、UNKNOWN
などを表します。多くのCライブラリは、エラー(または-1)を示すために0(例:INVALID_SOCKET
)を返すことでこれを効果的に行います。その他のオプションには、bool
へのポインターを渡すこと、およびエラー値を返すことが含まれます。このようなもの:
typedef enum square_err_t {
ERROR_NONE = 0,
ERROR_BAD_PTR,
ERROR_BAD_ROW,
ERROR_BAD_COL
} square_err_t;
square_err_t square_is_checked(Board* b, uint8_t row, uint8_t col, bool* is_checked)
{
...
}
これは個人的なプロジェクトであるため、assert
を使用して前提条件としてチェックを行うことができる場合があります。
bool square_is_checked(Board* b, uint8_t row, uint8_t col)
{
assert(b != NULL);
assert(col < b->width);
assert(row < b->height);
...etc.
}
これは開発中にエラーをキャッチしますが、チェックはリリースでコンパイルされます。
コンパイラまたは他のツールがそれをサポートしている場合は、関数に nullability注釈 で注釈を付けることができる場合があります。 (この例はSwiftの場合ですが、他の言語でも使用できるツールをクイック検索で表示します。)これらにより、関数への引数をnonnull
としてマークできます。つまり、引数をNULLにすることはできません。これにより、実行時ではなくコンパイル時に潜在的な呼び出しがキャッチされ、出荷前に修正できます。
あなたがするべき基本的な決定があります:
無効な入力は契約違反ですか、それとも予想されますか?
あなたはすでにBoard
へのポインターを与えるために呼び出し元に依存しており、正しいものをそれに向けています。なぜ無効な入力NULL
を特別なケースにしたいのですか、それはプログラマによるすべての論理エラーではありませんか?
範囲外の取締役会の立場は、はるかに多くについて議論することができます:
論理エラーに対応する方法は3つあります。
assert()
(リリースでは何もしないので、assertで寛大に)、リリースではabort()
。エラーの重大度に応じて、例外が未チェックの例外をスローする中程度に安全な言語では、条件が満たされる場合があります。