JavaerからPythonistaになった時に失敗したこと

  • 12
    いいね
  • 4
    コメント

初めに

これはPyCon JP Advent Calendar 2016の12/2担当記事です。
ほかにも面白い記事があるので、興味がある方はぜひ。
ところどころJavaを記述しているところがあるけれど、久々だから間違ってるかもしれないので、そこは雰囲気で察しながら読んでいただけると幸い。

目的

件名にもある通り、これを読んだ人がJavaer→Pythonistaへの移行をスムーズに行えるように、私が経験した失敗を共有しようと思う。

自己紹介

PyCon JP 2017のメンバー。
PyCon関連での活動でいえば、2015ではトークをしたり、2016ではLTしたりしている。
第一回Python Boot Camp講師もした。
Twitterアカウントは@horobi_gengar

今までの経緯

件名の件について、必要だと思うので簡単に今まで開発で使ってきた言語の説明を最初に。

順番 在籍期間 メイン言語 補足
1社目 2年半 特になし cとかvbとか。現場に合わせて臨機応変
2社目 5年半 Java 他にはJavaScriptとかDelphiとかCOBOLとか
3社目 5ヶ月 特になし C#やPHPをちょっとずつ
4社目 2年 Python ごくまれにRubyに関わる
5社目 3ヶ月(継続中) Python たまにJavaScriptを書く

なので、今回の話は4社目の時の話になる。

失敗したこと

1ファイル1クラスで書いてしまった

いろんな失敗をしたけれど、これが一番デカかった。
Javaは基本(例外あるけど)1ファイル1クラスで記述するが、Pythonは1ファイルに複数クラスを記述することが多い。
イメージ的にはPythonはJavaに比べてパッケージの階層が一つ減るイメージで書かなければならない。

パッケージ(フォルダ構成)のイメージと合わせて説明しようと思う。
例えば共通モジュールの中のusersテーブルにアクセスするためのDAOだったら

common
 └db
  └dao

というパッケージの下にUsers.javaというファイルを作成し、その中にUsersというクラスを作成する

そして、そのUsersクラスを他のクラスから使用したい場合、

Users.java
package common.db.dao.Users

users = new Users()
user = users.getUser(1)

みたいな感じになる。
しかし Pythonでも同様のパッケージ構成で記述してしまうと、

users.py
from common.db.dao.users import Users 

users = Users()
user = users.get_user(1)

みたいな感じになって、fromの最後とimportの内容がかぶってしまい、助長的になってしまう。
なので、今回の場合でいうと、dbパッケージの下にdao.pyを作成し、その中にUsers クラスを記述して、

users.py
from common.db.dao import Users 

users = Users()
user = users.get_user(1)

という風に記述すべき。(これはこれで、ゆくゆくdaoファイルのサイズがえらいことになりそうだが、あくまで例ということで)
この失敗により、ある程度稼働実績のあるモジュールをごそっと修正することになった。

内部変数を全てpublicにしてしまった。

Javaの場合、クラスの内部変数の頭にpublicやprivateとつければ、外から参照できるかどうかを明示的に指定することができる。

User.java

public class User{
    public int userId;          //クラスの外からアクセス可能
    private String userName; //クラスの外からアクセス不可能
    public User(int userId, String userName){
        this.userId = userId
        this.userName = userName
  }
}

User u = new User(1, "山田太郎")
System.out.println(u.userId)  //セーフ
System.out.println(u.userName)//アウト

一方、Pythonの場合、変数名に『_(アンダーバー)』をつけることにより、Javaでいうpublicやprivateの制御を行うことができる。(やろうと思えば参照できるけど。)

user.py
class User(object):
    def __init__(self, user_id, user_name):
        self.user_id = user_id       #クラスの外からアクセス可能
        self.__user_name = user_name #クラスの外からアクセス不可能

u = User(1, '山田太郎')
print(u.uer_id)     #セーフ
print(u.__user_name)#アウト

でだ、何を思ったのか、Pythonを書き始めた当時、勘違いして覚えてしまったのが、下記の言語仕様である

user.py
class User(object):
    user_id = 0                    #この場所に書いておけばクラスの外からアクセス可能
    def __init__(self, user_id, user_name):
        self.user_id = user_id       
        self.user_name = user_name #書いてなければprivate扱い

u = User(1, '山田太郎')
print(u.uer_id)   #セーフ
print(u.user_name)#アウト(だと思ってた)

どうしてこんなことになったのか。もちろんuser_nameは外からアクセスし放題である。
これに気付いた時背中に寒気が走り、アンダーバー2個付け祭りが開催されることになった。

getter・setterを作ってしまった。

Javaの場合、内部変数をpublicで指定せず、影響範囲等を考えて、getterやsetterを作る

User.java

public class User{
    private int userId;         //内部変数は外からアクセスできないようにしておく
    private String userName;
    public User(int userId, String userName){
        this.userId = userId
        this.userName = userName
    }
    public int getUserId(){
        return this.userId;
    }
    public int getUserName(){
        return this.userName;
    }
}

User u = new User(1, "山田太郎")
System.out.println(u.getUserId())  //内部変数を直接呼ぶのではなく、関数を挟むことにより、カプセル化する
System.out.println(u.getUserName())

なんせEclipseにgetter・setterを一括作成する機能があるぐらいだからね。
で、これと同じことをPythonでもやってしまった。

user.py

class User(object):
    def __init__(self, user_id, user_name):
        self.__user_id = user_id
        self.__user_name = user_name

    def get_user_id(self):
        return self.__user_id

    def get_user_name(self):
        return self.__user_name


u = User(1, '山田太郎')
print(u.get_user_id())
print(u.get_user_name())#こんな感じに使う

Pythonのような、いわゆる軽量言語のメリットの一つに、『コード量の少なさ』というものがある。
この悪しき記述方法はそのメリットを見事に消してしまう、ベリーバッデストノウハウである。残念。
Pythonの場合だと、もうこれでいいのだ。

user.py

class User(object):
    def __init__(self, user_id, user_name):
        self.user_id = user_id       #外から直接アクセスされる可能性があるなら、もうアンダーバーつけない
        self.user_name = user_name

u = User(1, '山田太郎')
print(u.user_id)
u.user_name = '鈴木二郎' #アクセスし放題だけど、これでいいのだ!!!

どうしても気になるならpropertyでも使えばいいじゃないってことです。

user.py

class User(object):
    def __init__(self, user_id, user_name):
        self.__user_id = user_id
        self.__user_name = user_name

    @property                          #読み取り専用ならこんな感じに
    def user_id(self):
        return self.__user_id

    @property
    def user_name(self):
        return self.__user_name


u = User(1, '山田太郎')
print(u.user_id)       #セーフ
u.user_name = '鈴木二郎' #アウト!!!!

ちゃんと書くって大事だよね。

あとがき

今までに何回か
「あなたの書いたPythonはJavaっぽい」
「俺Javaerなんだけど、あんたの書いたPython読みやすい」
と言われたことがあり、結構気にしていたのがきっかけで今回の記事を書いた。
やっぱり他の軽量言語をずっと書いてきた人が書いたPythonと比べると、自分の書いたPythonはどこか堅っ苦しくてJavaぽい。
もう2年Python書いてるので、Java臭はだいぶ消えてきていると思うのだが、これからも日々精進していきます。