さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

連想配列を使うかクラスを定義してインスタンスを作るべきかみたいな話

最近、連想配列を使うべきかクラス定義してオブジェクトを作るかみたいなところで非常によく悩んでいます。

例えば、エラーとかだとわかりやすいのですが

def validate(params):
    errors = []
    result = True
    if is_bad_params(params):
        errors.append('is_bad_params')

    if is_invalid_params(params):
        errors.append('is_invalid_params')

    if errors > 0:
        result = False

    return {
        'errors': errors,
        'result': result
    }

みたいにエラーを返すことってよくあると思います。
上記みたいなパターンって色んな所でイレギュラーなパターンのエラー処理をたくさん含めてしまったり、
実は返り値がとある場所では force_valid みたいなパラメータがついてたりとか。
連想配列って便利ですけど便利すぎるんですよね。。。。

でも上記みたいなのを量産してると正直管理しきれません。そういった場合僕は以下みたいな感じにしてます。

# coding: utf-8


class ValidateResult(object):


    def __init__(self):
        self.errors = []
        self.result = False


def validate(params):
    result = ValidateResult()

    if is_bad_params(params):
        result.errors.append('is_bad_params')

    if is_invalid_params(params):
        result.errors.append('is_invalid_params')

    if not len(result.errors):
        result.result = True

    return result

実際、どっちもどっちじゃんって思う方もいらっしゃると思うのですが、プロダクトの規模が大きくなってくると上記みたいにふんわりと構造を制限するだけでコードの可読性が変わってきます。
エラー処理みたいな大量に使うコードは僕は上記みたいにクラス定義して使っていくのが良いと思ってます。
その上で、他にも共通処理もたせられたらもっと理想ですね。

しかし、クラスを定義するのはそれなりに時間がかかります。なぜならちゃんとした設計でやらないと連想配列を使う場合よりしんどいことになるからです。
なんとなくで構造を縛ってしまうと後々使いづらい構造になりかねません。ちゃんと時間をかけて熟考することが大事です。

まぁ、僕もどのくらい共通して使われる場合はクラスにするかみたいな勘所は正直全然ありません。
なんとなく思いつきで書いたコラムみたいな話でした。

プログラムでtransactionをはったらcommitかrollbackで終わらせるようにしよう

みなさんtransaction使ってますか?transactionは便利で使う機会がとても多いと思います。 しかし皆さんちゃんとcommitとrollbackを設定していますか?
まぁセッションからコミットされない限りデータに変更ないしヘーキヘーキ!みたいなことを思ってる方もいるかもしれません。

しかしそれは危険です。僕はしっかり設計不備で危険を踏み抜きました。

以下に例を紹介します。

入れ子transaction

pythonっぽい疑似コードで示します。

def sample_inner():
    try:
        # トランザクション開始
        # コミット
    except Exception as e:
        pass

try:
    # トランザクション開始
    sample_inner()
    # コミット
except Exception as e:
    # ロールバック

上記のようなコードがあるとします。
こちら、 sample_inner() 内で例外が発生した際にはロールバックが発生しません。
そうなると、呼び出し元のコミットが発生してしまってトランザクションとコミットの組み合わせにずれが出てしまいます。

こんなの入れ子構造のtransactionにしたやつが悪いよと思うあなた。正解です。
しかし、やむを得なく実装が複雑になったり、最近はデコレーターでtransactionがはれてしまうので、一旦はってしまうみたいな運用をしてしまったりと事故が起こる可能性は大いにあります。
人は完璧なわけではないので、複雑になれば複雑になるほどこのような事故がおこりやすくなります。
特に上記のようなコミットでズレが起きてしまうと、コミットされてほしくないデータがコミットされたりと大事故につながる可能性があります。お金周りで発生なんてしたら目も当てられません。
その場合は、ロールバックしておいたほうがまだ安全でしょう。入れ子transactionでロールバックはモノによって挙動が違いますが、少なくともコミットされることはありません。

もちろんできるだけシンプルに、入れ子にしないようにするのが一番です。ですがそうはいかないのが現実です。
細かいところからできるだけ安全にコードが組めるように注意すると良いでしょう。

MySQLの権限設定のやりかた、RDSの場合の特別な設定も

MySQLの権限設定で困ったのでメモ。 RDSだとちょっと使えない文字列があるので注意です

普通にGRANT設定するだけ

GRANT SELECT,INSERT,UPDATE,DELETE ON `DB名`.* TO `ユーザ名`@* IDENTIFIED BY 'パスワード';

上記のSELECTやINSERTのところに許可したい権限を入れるだけですね。
ALTER文やCREATE文を発行するような場合は上記だけだと駄目なので注意しましょう。
マイグレーションアクセスでもない限り上記でだいたい大丈夫でしょうね。

RDSでGRANT設定

RDSだと実は前述のままだとエラーを吐きます。
なぜなら、 *ワイルドカードが利用できないからになります。

GRANT SELECT,INSERT,UPDATE,DELETE ON `DB名`.* TO `ユーザ名`@`%` IDENTIFIED BY 'パスワード';

上記のように、 % で代替できます。 % はどのホストからでも接続できるという意味になります。
* だと localhost も接続できるというのでセキュリティ上の都合でしょうか? まぁそれだったら % 許すなよって気もしますね。

また、サーバのIPやhostが特定できる場合はちゃんとhostの箇所は固定設定しましょう。

PythonでLRUキャッシュを使って負荷軽減と高速化

皆さん、PythonにもPHPのOPCacheのようなキャッシュ機能があったのをご存知ですか?
そんなLRUCacheの紹介をしようと思います。こちらはPython3.2以降で使える機能です。

docs.python.org

LRUCacheは

LRU (least recently used) cache という技術をつかっています。具体的には未使用な期間が長いキャッシュを破棄していくアルゴリズムですね。
とはいえ使う上でそこまで気にしなくて良い話です。

使い方は以下のようになっております。

@lru_cache(maxsize=32)
def get_pep(plus_1, plus_2):
    return plus_1 + plus_2

デコレータとして利用すると良いようです。
上記使い方を見て分かる通り、関数の返り値をキャッシュします。

maxsizeというのは、LRUでキャッシュされる回数になります。つまり、上記だと32回キャッシュされたら、最も使われていないものから削除されていきます。
キャッシュを削除するための機能として、 cache_clear() も提供されています。こちらを用いて時限式でキャッシュを削除していくことも可能です。

redisなどと違って、10分だけキャッシュするみたいな仕組みは自分で作らないといけません。そこを鑑みてもredisよりも導入コストは小さいかなと思っています。
通信も発生しないですしこちらのほうが高速なイメージが強いですね。

ssh接続時のポート変更の際にSELinuxの設定を変更する

ssh接続してますか?
ssh接続のポートを変更する際はいろいろな設定を変更しなければいけませんよね。

sshのコンフィグ、iptables、 もしAWS使ってたらセキュリティグループもいじらないといけません。

しかし実は SELinux もいじらないといけないんです。上記だけ変更して満足したら、ssh接続できない。。。なんてことになりがちです。

SELinuxでの接続設定の確認から変更

$ semanage port -l |  grep ssh
ssh_port_t                     tcp      22

上記コマンドで、SELInuxがどのポートでの接続を許可しているかを確認することができます。
sshgrepしなかったら各種いろいろなポート制限を見ることができるので面白いですよ。

さて、上記だと22番ポートだけに限定されているようです。つまり22番ポート以外で接続するには上記に該当のポートを追加しなければいけません。

$ semanage port -a -t ssh_port_t -p tcp 20000
$ semanage port -l |  grep ssh
ssh_port_t                     tcp      22, 20000

上記コマンドでOKです。わかりやすいコマンドですね。

SELinuxにはいろいろな制限があります。めんどくさいから解除なんてこともよく聞きますが、脆弱性への対策としては心強い味方です。
SELinuxとは上手につきあえる道をさがしていきましょう。

MySQLの文字列から数値への型変換について

MySQLで文字列から数値の型変換でちょっと変換のされかたが特殊だったので紹介します。 公式のドキュメントはこちら

MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.10 キャスト関数と演算子

明示的な型変換

型変換というのはだいたいアプリケーション側でやるものであんまりMySQL側でやることは無いとは思いますが、明示的な型変換は以下のように行います。

SELECT CAST('1' AS SIGNED);

こちらの結果は 1 になります。
別に驚きの要素はないですね。

では、次の例です。

SELECT CAST('1_d' AS SIGNED);

実はこちらの結果も 1 になります。数字の後の文字列が切り捨てられるようですね。 Pythonなどでキャストするとエラーが出る例なので、違和感を覚えるかもしれませんが、 MySQLではこれが仕様です。

SELECT CAST('d_1' AS SIGNED);

ちなみに、こちらだと 0 になります。別に数字が入っていれば良いというわけではありません。
その上でエラーにもならず 0 になるので気をつけたほうが良いでしょう。

暗黙的な型変換

じゃあ、型変換しなければいいじゃない。と言われるかもしれませんが、MYSQLには暗黙の型変換というものが存在します。
例えば、以下の例です。

SELECT '1_d' + 1;

こちら、結果は 2 になります。
文字列の箇所が勝手に数値に変換されたのです。

こんなことしねーよというあなた。実はこれは大きな罠になるのです。
テーブルに id を振ってインクリメンタルな整数の数値にする方はいらっしゃると思います。
その時にこの暗黙の型変換が罠になることがあります。

SELECT FROM sampletable where id = '1_d';

こちら勘の良い方ならお気づきだと思いますが、以下の例と同等になります。

SELECT FROM sampletable where id = 1;

勝手に変換される方が安全という説もありますが、こちら全然意識してなかった方はいらっしゃるのではないでしょうか?
数値型が入ってくる想定の箇所では暗黙の型変換がされてしまうようです。

すべてを調べたわけではありませんが、このような仕組みになることを意識しないでいると思わぬバグにつながるので気をつけたほうが良いでしょう。

iOSアプリ × Swiftでsegueで遷移する際にデータを受け渡すやりかた

segueで遷移する時になにか初期データを渡して遷移させたい?そんなことを考えたことがありませんか?
遷移時にAPIを呼び出したりするのもUX的にちょっとなぁ。。。みたいなことはあると思います。

そんな時はsegue遷移時の以下のコードのようにして定義できます。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "toNextView"{
        let nextViewController: NextViewController = segue.destination as! NextViewController
        nextViewController.next_view_data = "なにかここにデータ"
    }
    
}

上記は皆様同じのprepareメソッドでsegueで呼び出されるメソッドです。
こちらで次の画面のインスタンスを生成して遷移するのが常道ではないでしょうか?
その時にちょっぴり手を加えてプロパティなどを追加すると遷移時にデータを渡すことができます。

便利なので是非使ってみてください。