くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

【今週水曜】糖質制限エンジニアのための採用説明会開催

概要

https://user-images.githubusercontent.com/40745943/57514190-d59b2700-734a-11e9-9337-b4cbe04470df.JPG

糖質制限エンジニアのための採用説明会」とは...

「身体と技術を高めたい!」「筋トレできる会社はどこだ!?」というエンジニアを対象に、ゆでたまごやサラダチキンといった低糖質フードを振るまいます。さらに、弊社CTOによる魂を込めた演説が行われる一大イベントとなっております。

  • レーニングジム完備。ぜひご利用ください。
  • 筋トレや低糖質フードに興味がないエンジニアも、もちろん大歓迎です。

開催日時

2019年5月15日(水) 19:00~21:00

開催場所

みんなのマーケット株式会社オフィス
東京都品川区西五反田4丁目31−18目黒テクノビル3F

参加対象

※ ママやパパはぜひ子どもと一緒に!

準備するもの

お名刺1枚

参加方法

下記よりお申し込みください。
https://minma.connpass.com/event/129442/

登壇者情報

みんなのマーケット株式会社CTO 戸澤拓也

1990年7月5日、秋田県生まれ。
成蹊大学理工学部情報科学科にて「HTML5による音声通話ソフトウェアの開発」を研究。2013年3月に同大学を卒業。
2013年4月、みんなのマーケット㈱入社。

タイムテーブル

時間 内容
18:30~19:00 受付
19:00~19:05 開会のあいさつ
19:05~19:50 CTO登壇
19:50~20:00 質疑応答
20:00~20:45 懇親会
20:45~20:50 閉会のあいさつ
20:50~21:00 来場者退場

※ CTO登壇の内容は、会社説明・技術説明・ビジョンについて・福利厚生について

みんなのマーケット株式会社について

みんなのマーケット株式会社では、ハウスクリーニングや引越し、家の修理やリフォームを始めとする生活関連の出張・訪問サービスに特化したインターネット商店街である「くらしのマーケット」を運営しています。くらしのマーケットでは、様々なジャンルの出張・訪問サービスを口コミや料金で比較して、オンラインで予約することが出来ます。

https://user-images.githubusercontent.com/40745943/57514446-886b8500-734b-11e9-811b-bb733d797cd7.JPG

技術コミュニティ支援のために、IT勉強会向けに会場を貸します!(🍻 +🍕 あり)

f:id:curama-tech:20190510122956j:plain

オフィス移転のため、受付停止中です(2020年10月現在)

みんなのマーケットCTOの戸澤です。

みんなのマーケットは、2019年3月18日に東京本社を五反田から不動前に移転し、約2.5倍に増床しました。

不動前の新オフィスでは、セミナールームとオープンスペースを新設しております。 この度、技術への恩返しの意味を込めて技術コミュニティの活性化に貢献するために、IT勉強会向けに会場貸しを行います。

また、要相談ですが、勉強会参加者へのビールとピザの提供を予定しています。

会場

セミナールーム、オープンスペースの利用が可能です。

  • 収容人数: セミナールーム30名, オープンスペース50名〜(応相談)
  • 設備: Wi-Fi, 電源
  • 備品: 50インチモニター2台、プロジェクター, マイク+スピーカーシステム
  • フード: ビール, ピザ(要相談)

参考写真(セミナールーム) f:id:curama-tech:20190510123719j:plainf:id:curama-tech:20190510123730j:plain

アクセス

東急目黒線 不動前駅より徒歩2分 (不動前駅は、JR目黒駅から1駅です)

東京都品川区西五反田4-31-18 目黒テクノビル 3F https://goo.gl/maps/ao2d6Fam4UgeukMk8

問い合わせ/申込み/下見

社内での利用もありますので、まずはtwitterかメールで問い合わせいただければと思います。

皆様のご利用をお待ちしております。

設計の原則の紹介とくらしのマーケットのシステム

f:id:curama-tech:20181220111408p:plain

自称webエンジニアの高橋です。ソフトウェアを設計するにあたってよく知られている原則に、SOLID原則というものがあります。これは、

  • 単一責任の原則(Single Responsibility Principle)
  • オープン・クローズドの原則(Open-Closed Principle)
  • リスコフの置換原則(Liskov Substitution Principle)
  • インターフェース分離の原則(Interface Segregation Principle)
  • 依存関係逆転の原則(Dependency Inversion Principle)

の5つの原則の頭文字を並べたものです。

本記事では、くらしのマーケットのプロダクションコードの一部を例/反例として、上の原則の紹介をしてみます。なお、以下に紹介するコードは全てTypeScriptです。

単一責任の原則

単一責任の原則はモジュール(クラス)を変更する理由は1つ以上存在してはならないという原則です。 例えば以下のようなコードがAngularのコンポーネントとして定義されているとします。

@Component({
    selector: "terry-ticket-list",
    templateUrl: "./terryTicketList.html",
    styleUrls: ["./terryTicketList.scss"]
})
export class TerryTicketListComponent implements OnInit {
    // 省略

    private getTerryTickets(
        params: GetTerryTicketListParameters,
    ): void {
      // 100行に渡るajax処理
    }
}

Angularのコンポーネントはviewに対する最小限のロジックのみを書くべきですが、このクラスではデータ取得処理を長々とコンポーネントに記述してしまっています。 例えばこの状態で、データ取得処理のモジュールを入れ替えたいという要件が発生した場合(例えば@angular/http@angular/common/httpに変更したいときなど!)、 viewには直接関係ない理由でこのコンポーネントを修正する必要が出てきます。

この場合は以下のように ticketApiService 等の別のモジュールを作成して、処理を委譲してしまうことで解決できそうです。

@Component({
    selector: "terry-ticket-list",
    templateUrl: "./terryTicketList.html",
    styleUrls: ["./terryTicketList.scss"]
})
export class TerryTicketListComponent implements OnInit {
    constructor(
        private ticketApiService: TicketApiService,
    ) {
    }

    // 省略
    private getTerryTickets(
        params: GetTerryTicketListParameters
    ): void {
      return this.ticketApiService(params);
    }
}

これで、viewに関するロジックとデータ取得のロジックが分離されました。

オープン・クローズドの原則

オープン・クローズドの原則は、モジュール(クラス)は拡張に対して開いていなければならず、修正に対して閉じていなければならないという原則です。 ところで、拡張に対して開いているとはどのような状態でしょうか?

export class TerryTicketDetailComponent implements OnInit {
    @ViewChild("placeholder", { read: ViewContainerRef })
    public placeholder: ViewContainerRef;
    private componentRef: ComponentRef<TerryCardComponent>;

   // 省略
    private initChildComponent(): void {
        let currentComponent: typeof TerryCardComponent;
        switch (this.terryTicket.ticket.terryTicketTypeId) {
            case TerryTicketTypes.AlertPostBadReview:
                currentComponent = CardPostBadReviewComponent;
                break;
                // 省略
        }
        const componentFactory = this.compiler.resolveComponentFactory<TerryCardComponent>(currentComponent);
        this.componentRef = this.placeholder.createComponent(componentFactory);
    }
}

例えば上のコンポーネントでは、initChildComponent関数の中で子コンポーネントをswitch文で動的に設定しています。ここで、子コンポーネントをさらに追加したい場合は、case文を新たに追加して修正する必要があります。 このままでもストラテジーパターン的な実装ではありますが、さらに責務を分離するなら、以下のような関数を定義すると良いでしょう。

export class TerryTicketDetailComponent implements OnInit {
    @ViewChild("placeholder", { read: ViewContainerRef })
    public placeholder: ViewContainerRef;
    private componentRef: ComponentRef<TerryCardComponent>;

   // 省略
    private initChildComponent(): void {
        const currentComponent = this.dispatch(terryTicketTypeId)
        const componentFactory = this.compiler.resolveComponentFactory<TerryCardComponent>(currentComponent);
        this.componentRef = this.placeholder.createComponent(componentFactory);
    }

    private dispatch(ticketTypeId: TerryTicketTypeIds): TerryCardComponent {
        const components = this.getComponents();
        for (const component of components) {
          if (ticketTypeId === component.ticketTypeId) {
            return component;
          }
        }
        return defaultComponent;
    }
}

ここでinitChildComponentメソッドに着目すると、componentを追加(== 拡張)する際に、コードの変更(== 修正)が必要なくなったことがわかります。つまり、initChildComponentレベルで、拡張に対して開いているということになります。なお、クラスレベルでオープン・クローズドの原則を担保する場合は、dispatch関数を別クラスに定義することで、さらに責務を分離できるでしょう。 また、例えばここで getComponentsメソッドを動的にあるディレクトリ配下のcomponentを取得するような処理にするようにすれば、ファイルを追加するだけで上記のコードをオープン・クローズドの原則に従った形で拡張することができます(実際はそこまでせずに、getComponentsの関数の中は追記可とする設計でも、ある程度は結合度を下げられるのでそれで十分という場合もあるかと思います)。

リスコフの置換原則

リスコフの置換原則は、派生型(子クラス)は基本型(親クラス)と置換可能でなければならないという原則です。子クラスで不用意に親クラスのメソッドを上書きしたりすると、子クラスと親クラスが置換不可能になってしまい、クラスのis-A関係が崩れてしまいます。

私達のコードではリスコフの置換原則に反したコードは見つかりませんでしたが、今後も注意してコーディングしていきたいです。

インターフェース分離の原則

インターフェース分離の原則は、クライアントに、クライアントが利用しないメソッドへの依存を強制してはならない。という原則です。 例えばメソッドAとメソッドBがあるインターフェースを利用するクライアントが常にメソッドAしか実行しない場合、メソッドBへの依存は無駄となってしまいます。

export interface IApiPageLogic {
    reverseGeocoding(
        locationIds: string[],
        serviceTypeId?: string,
        pattern?: MasterApiModels.AreaFetchingPatterns
    ): Promise<LocationInfoWithRelaseFlag[]>;
    getStoreAccessToken(
        storeId: string,
        platform: TwilioPlatforms
    ): Promise<CapabilityToken | undefined>;
}

上記のインターフェースは地理情報の処理メソッドと認証情報の取得メソッドが同居しています。そして、これれらのメソッドを必要としているクライアントも別々のクラスです。

この場合は、

export interface IGeocodingLogic {
    reverseGeocoding(
        locationIds: string[],
        serviceTypeId?: string,
        pattern?: MasterApiModels.AreaFetchingPatterns
    ): Promise<LocationInfoWithRelaseFlag[]>;
}


export interface IAuthTokenLogic {
    getStoreAccessToken(
        storeId: string,
        platform: TwilioPlatforms
    ): Promise<CapabilityToken | undefined>;
}

このように、ユースケースを考慮して別々のインターフェースとして分離したほうがより良い設計かと思います。

依存関係逆転の原則

依存関係逆転の原則は、 上位レベルのモジュールは下位レベルのモジュールに依存すべきではない。両方とも抽象に依存すべきである。 という原則です。

くらしのマーケットのNode.jsのコードでは、現在DIの仕組みを取り入れており、上位のモジュールは下位のモジュールのインターフェースに依存するようになっています。

@Dependency.register()
export class CalendarManager implements ICalendarManager {
    static $inject: string[] = ["CalendarApi"];
    constructor(private calendarApi: ICalendarApi) {}
    // メソッド定義
}

例えばこのCalendarManagerクラスは、CalendarApiをインジェクションしていますが、その機能には ICalendarApiインターフェース経由でしかアクセスできません。この場合、コンストラクタ引数のcalendarApiがどのように具体的に実装されているか、CalendarManagerクラスは知ることはできません。このようにして、上位モジュールと下位モジュールの結合度を下げることができます。

まとめ

以上、くらしのマーケットのコードを例にして、SOLID原則の紹介をしてみました。これからも設計を意識しながら、保守しやすいコードを目指して開発していきたいです。 くらしのマーケットのシステムに興味がある方はコーポレートサイト https://www.minma.jp/までお気軽にご連絡ください。

「話だけ聞きたい!」ってことでオフィスに遊びにきていただくことも可能です。 話を聞いてみる / みんなのマーケット株式会社

参考

PythonでMeCabとBeautifulSoupをおいしくいただきたくなったので簡単なクローラーを作ってみた

こんにちは、エンジニアのつづきです。

最近、社内でちょくちょくSEOが。。。
とか、
クローラーが。。。

とかいう話を耳にするようになりました。

クローラー」いい響きですね。

鍋も美味しいシーズンになってきましたから、Pythonを使ってMeCabとBeautifulSoupをおいしくいただくのもいいですね。(全く関係ない)

https://user-images.githubusercontent.com/40745943/50324643-79627280-0523-11e9-954e-6b49857291ee.JPG (オフィスにて会社の皆さんとやった火鍋)

ということで、今回は、Pythonで簡単なクローラーを作ります。

そもそもクローラーとは、

WEB上のファイル(HTML文書だけでなく、画像・PDFまで含む全般)を収集するためのプログラム[1]

らしいです。

では早速作っていきましょう。
用意するものは、以下の通りです。

Python 3.5.0
MeCab
Beautifulsoup4
urllib

まずは、どこかのWebサイトにアクセスするコード[2]を以下に記述します。

import urllib.request
def getHTML(url):
    return urllib.request.urlopen(url)

とっても簡単ですね。
次は、WebページからHTMLを取得するコード[3]を以下に記述します。

from bs4 import BeautifulSoup
def soup(html):
    return BeautifulSoup(html, "html.parser")

これもまた3行でできてしまいました。
取得できた結果は、以下のようになります。

<中略>
<!--
                                ....JgNNMMMMMMMN;
                           ..gNMMMMMMMH""""77TMM#
                      ..&MMMMMH""!            dMM[
                   .(NMMMB"!                  ,MM#
                ..MMMM"!                       dMM;
              .gMMMY`            .+g,    .gg,  (MMb
            .dMM#=               MMMM:  .MMMM~  MMN.
           (MM#^                  ?7` ` ` ?!    JMM]
         .MMMY                                  .MMN
        .MM#!             ..JJ-..............((+NMMM:
       .MM@              .MMMMMMMMMMMMMMMMMMMMMMMB"!
      .MM@             .dMMD
     .MM#             .dMMC
     jMM%             dMM%                                  < 仲間募集!
    .MM#             .MMF                                       http://www.minma.jp/recruit/
    .MMb             JMM\
    .MMb             (MML
    .MMN              dMMm.                   ..gMM]
     dMM]              ?MMMNgJ...      ...(JgMMMMMMb
      WMM,               _TMMMMMMMMMMMMMMMMMMB"!.MMb`
       TMMm.                  _??77"777?!`      .MMb
        (MMMm,                               `..(MMb
          ?WMMNa...                    ....&NMMMMMB!
             ?WMMMMMNNgggJJ(((JJ&gggNMMMMMMM#"=!
                 ?7TWHMMMMMMMMMMMMMHB""7!
-->
<中略>

次は、取得したHTMLから<p>を取得してくるコードを以下に記述します。

def getPTags(soup):
    return soup.find_all("p")

今度は2行でできてしまいました。
結果は以下のようになります。

<span class="tag">特集</span></p>
<p class="text-sub">年末大掃除</p>
<p class="ttl">がんばらない大掃除<br/>のススメ2018</p>
<span class="tag">特集</span><span class="new">NEW</span></p>
<p class="text-sub">かゆいところに手がとどく</p>
<p class="ttl">引越し関連サービス</p>
<span class="tag">マガジン</span></p>
<p class="text-sub">利用者の声</p>
<p class="ttl">くらしのマーケットとメルカリで引越しが完結した!</p>
<span class="tag">マガジン</span></p>
<p class="text-sub">利用者の声</p>
<p class="ttl">引越しの救世主!?直前予約でもスムーズに引越せた!</p>
<span class="tag">マガジン</span></p>
<p class="text-sub">利用者の声</p>
<p class="ttl">最初は友人の紹介で。僕も友人に紹介してます!</p>
<以下略>

次は、取得した<p>から、文章するコード[3]を以下に記述します。

def getStrings(tags):
    string = ""
    for p_tag in p_tags:
        string += re.sub(r'<.*?>','', str(p_tag)) + “\n”
    return string

上記のコードでは、<.*?>という正規表現でタグのみを空の文字列と入れ替えています。
結果は以下のようになります。

特集
年末大掃除
がんばらない大掃除のススメ2018
特集NEW
かゆいところに手がとどく
引越し関連サービス
マガジン
利用者の声
くらしのマーケットとメルカリで引越しが完結した!
以下略

次は、MeCabを使って、形態素解析をしてみましょう。
しかし、そもそも形態素解析とはなんでしょうか?

コンピューターの自然言語処理の一。与えられた文章を、辞書データなどを用いて形態素の単位に区切り、品詞を判別する処理。[4]

らしいです。
では早速上記で得られた結果を用いて形態素解析をしてみましょう。
MeCabを利用した形態素解析を行うプログラムを以下に記述します。

    mecab = MeCab.Tagger ("-Ochasen")
    mecab_string = mecab.parse (string)
    for ms in mecab_string.split("\n"):
        word = ms.split("\t")[0]

結果は以下のようになります。

特集
年末
大
掃除
がんばら
ない
大
掃除
の
ススメ
2018
特集
NEW
かゆい
ところ
に
手
が
とどく
引越し
関連
<以下略>

どうやら、Pythonを使ってMeCabとBeautifulSoupをおいしくいただくことができました。

今回ご紹介したようなクローラーを使えば、Webページ上のデータを取得して、様々な分野のトレンドなどの情報収拾に利用できたりするかもしれません。
同時に複数のページを解析するのはどうするの?とか、HTMLの解析で複数のタグに囲まれている要素はどうするの?とかはまた別のお話

最後に

私たちテックチームでは「くらしのマーケット」を一緒に作る仲間を募集しています。

Pythonを使って開発をしたいな」
「火鍋一緒に食べたいな」

ご興味のある方、ご応募お待ちしております。 http://www.minma.jp/careers/

「話だけ聞きたい!」ってことでオフィスに遊びにきていただくことも可能です。 話を聞いてみる / みんなのマーケット株式会社

お気軽にご連絡ください!

参考文献

[1] https://www.seohacks.net/basic/terms/crawler/
[2] https://docs.python.jp/3/howto/urllib2.html
[3] https://qiita.com/itkr/items/513318a9b5b92bd56185
[4] https://kotobank.jp/word/%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90-3115