プロジェクトを管理するときに大事にしていること

特に出典とかはありませんが僕の経験から大事にしていることなどを書きました。 社内向けのドキュメントに書いていましたが、外にも出せるかなと思い少しだけ修正しつつ記事にしました。

全体像をきちんと理解する

  • 全体像を理解するとはプロジェクトの5W1Hを理解するということ
    • Why: なぜそれが必要なのか、誰が喜ぶのか
    • What: 何をつくるのか、何が必要なのか
    • When: いつまでにつくりたいのか
    • Who: 誰が関わり、誰がつくるのか
    • Where: どこにつくるのか、どこでつくるのか
    • How: どういう方法でつくるのか
  • 全体像を把握しないと詳細に落とし込むことができない
    • 理解してない状態で詳細に落とし込むと認識違いの手戻りや仕様変更の確率があがる
    • 疑問点は知ってる人に聞きまくって理解する
  • 把握した上でだいたいのスケジュール感でどこまでのものが提供できるのか想像する

なぜ自分がこのプロジェクトに参加しているのかを考える

  • どういう役割を求められているのか
  • 自分の何がプロジェクトに活かせるのか
  • 自分がこのプロジェクトを完了させたときに何が得られているのか
  • 何をモチベーションにしていくか
  • 「自分のやりたいこと」と「リリースまでのスケジュール」の交わる点がプロジェクトのゴール
    • 「自分のやりたいこと」だけやってもスケジュールが伸びるだけ
    • 「リリースまでのスケジュール」だけを優先してもモチベーションが上がらない
    • ちょうどいい感じのポイントを探す

管理する = 偉いではない

  • プロジェクト管理することは偉いことではない
    • 単純にそういう立場であるだけ
    • 求められているものがプロジェクトを管理することであるだけ
  • 人を管理するではない、タスクを管理する
  • 上からの操り人形みたいなイメージではなくむしろ下から押し上げていくイメージ

不確実性コーンを意識する

こういうやつ

  • どんなに仕様を理解して全体を把握していても考慮漏れや仕様変更が必ずどこかである
    • 今まで完璧にタスク分解できて考慮漏れがなかったことは一度もない
  • 人間はリリースが先であるほど余裕をカマすのでスケジュールが巻くことはまずない
    • 不確実性コーンで言えば下半分はほぼない
    • もし余裕があれば(だいたいないけど)
      • テストの拡充や自動化で品質を上げる
      • リファクタリングなどで技術的負債を少なくする

定期的にスケジュールを見直す

  • スケジュールは基本遅れるものとして考える
    • スケジュールをわざと遅らせてやろうと思っている人間はいない
    • 遅れることは仕方がないのでそこから未来のことを考える
  • 遅れが発生した時に都度スケジュールを見直す
  • 「スケジュールを見直す」とは
    • プロジェクトの開始から見て見積り精度がどうだったかを見直す
    • タスクの漏れがないかどうか見直す
    • タスクの優先度を見直す
    • メンバーのアサインを見直す
    • スケジュールを引き直す
  • 見直しをしないといつまでも不確実性が小さくならない
  • 見直したスケジュールは共有する

ツールはなんでもいい

  • 定期的に見直すことさえすれば正直なんでもいい
  • ツールにこだわりすぎるとツール自体に時間をかけてしまったり逆に制限がかかったりする
  • 要は第三者に遅れているのか、順調なのか、間に合うのかが説明できればいい
  • あとは自分が見直しやすい感じで

決して無理をしない

  • 今いるメンバーで可能な範囲でつくる、スケジューリングする
    • リリースまでに間に合うか
    • レビューが可能か
    • リリース後のメンテナンスが可能か
  • 背伸びしすぎない
    • 「今流行りらいしこれ使おうぜ」
      • 誰もメンテナンスできなくて最悪みたいなことが起こる
    • 今いるメンバーがちょっと学びがあるくらいがちょうどいい
  • 人間がこなせるタスク量は限界がある
    • 無理をすると絶対にどこかがほころぶ
    • 身体的、精神的な疲労は慢性的に蓄積する

情熱みたいなものも時には必要

  • 人間は感情で動く生き物
  • 情熱や熱意は人に影響を与える
  • 「あなたがそう言うなら」といかに人に思ってもらえるか
  • スキルも大事だけど最終的には人間性がものをいう

エンジニアのアウトプットについて社内勉強会で発表した

だいたい月に1回ほど福岡拠点のエンジニアが集まってtech talkというイベントをしていて、今回エンジニアのアウトプットというテーマで発表した。

前職はアウトプットすることが企業理念の1つとして入っている会社だった。社員みんなアウトプットを意識している中で私も働き、アウトプットすることで成長してきた部分もあると感じているので、今一度アウトプットの良さについて自分なりに噛み砕いて資料にしてみた。

speakerdeck.com

今のところアウトプットは自己成長を目的にするというのが根底にある思っている。自己成長の効率をより高める手段として、他人に見えやすいようにアウトプットすることでフィードバックをもらったり評価を受けたりすることが良いのではないかという話。

周りの同僚の反応はなかなかよかったが、自分としてはもう少しデザインをよくしたかった。個人的に、エンジニア向けの登壇資料だから字ばかりでも伝われば大丈夫みたいなのは今後はあまりやらないようにしようとしていて、何か発表するならできるだけデザインもこだわって資料を見ただけで伝わるように作っていこうと思っている。デザイナーさんにデザインを教えてもらうぞー。

styled-componentsのThemeProviderをつかってReactのスタイルを管理する

Reactのスタイル管理、どうしていますか。

いろいろな方法があると思いますが、大規模なReactでのフロントエンド開発においてCSSをどう管理するかとても悩みました。私の所属しているチームでは、styled-componentsのThemeProviderを使ってスタイルを管理することにしましたので紹介します。

コード例

例えば簡単なButtonコンポーネントを実装し、propsで背景色を変えたい場面があったとします。

import React from "react";
import styled from "styled-components";

const StyledButton = styled.button`
  background-color: ${props => (props.color === "primary" ? "blue" : "gray")};
`;

export default function Button({ color, children }) {
  // わかりやすいようにpropsを分けて渡しています
  return <StyledButton color={color}>{children}</StyledButton>;
}

この実装だとcolor propsの種類が増えたときに分岐の実装が増えていくため、objectに設定を切り出して書いたりします。

const backgroundColor = {
  default: "gray",
  primary: "blue",
  warning: "orange"
}

const StyledButton = styled.button`
  background-color: ${props => backgroundColor[props.color]};
`;

上の実装でだいぶ変更には強くなります。ただ、もしcolorのprimaryやwarning設定をある一部分だけ違う色に変えたいという場面があったとします。Buttonコンポーネント内で色を固定してしまっているため、propsを追加するか、styled-componentsのcss propsを渡すか、もう一つButtonをラップしたコンポーネントを作るなどの方法を取らねばなりません。

そういった場合にThemeProviderを使って実装すると便利なことがあります。

// ButtonGroup.js(Button.jsを使う側)

import React from "react";
import { ThemeProvider } from "styled-components";
import Button from "./Button";

const theme = {
  button: {
    backgroundColor: {
      default: "gray",
      primary: "blue",
      warning: "orange"
    }
  }
};

export default function ButtonGroup() {
  return (
    <ThemeProvider theme={theme}>
      <Button color="default">Default</Button>
      <Button color="primary">Primary</Button>
      <Button color="warning">Warning</Button>
    </ThemeProvider>
  );
}
// Button.js

import React from "react";
import styled from "styled-components";

// ThemeProvider内のコンポーネントはpropsにthemeがセットされている
const StyledButton = styled.button`
  background-color: ${props => props.theme.button.backgroundColor[props.color]};
`;

export default function Button(props) {
  return <StyledButton {...props} />;
}

Buttonコンポーネントを使う側(ButtonGroup.js)でThemeProviderを使い theme propsで色を設定しているobjectを渡します。ThemeProviderで囲まれた全コンポーネントには theme というpropsが自動で追加されており、 props.theme にはThemeProviderで渡したobjectが入っています。

これでコンポーネントを利用する側からスタイルをカスタムしやすくなりました。同時にButtonコンポーネントではCSSとしてThemeProviderからの値をセットするだけで、背景色自体の管理はしなくなっています。

以下のようにobjectを書き換えれば、一部分だけ設定を変更することができます。

// ButtonGroup.js

import React from "react";
import { ThemeProvider } from "styled-components";
import deepmerge from "deepmerge"
import Button from "./Button";

const defaultTheme = {
  button: {
    backgroundColor: {
      default: "gray",
      primary: "blue",
      warning: "orange"
    }
  }
};
const customTheme = deepmerge(defaultTheme, {
  button: {
    color: {
      primary: "green",
      warning: "red"
    }
  }
});

export default function ButtonGroup() {
  return (
    <ThemeProvider theme={defaultTheme}>
      <div>
        <Button color="default">Default</Button>
        <Button color="primary">Primary</Button>
        <Button color="warning">Warning</Button>
      </div>
      <div>
        <ThemeProvider theme={customTheme}>
          <Button color="default">Default</Button>
          <Button color="primary">Primary</Button>
          <Button color="warning">Warning</Button>
        </ThemeProvider>
      </div>
    </ThemeProvider>
  );
}

ThemeProvider自体はReactのContextの仕組みを使っているので、一番近いところで渡されているThemeProviderの値がセットされるようになっています。カスタムしたい場合は、デフォルトのテーマ設定objectからマージして書き換えたい部分のみ上書きするようにします。

これでcustomThemeを渡した方のみカスタムした背景色が当たるようになりました。

試してみたソースコードはこちらに置いています。

ThemeProviderを利用するメリット

コンポーネントとスタイル管理を切り離して管理できる

上記の例ではButtonコンポーネント内で背景色のスタイル管理をしなくなりました。つまりスタイル変更のためだけにButtonコンポーネントを修正する必要がなくなるということです。背景色だけでなくサイズやテキストの色などもThemeProviderで管理するようにすることですべてコンポーネントを利用する側でスタイル管理ができるようになります。

ただStorybookでコンポーネントカタログを作るときなど、ThemeProviderを利用せずコンポーネントのみで表示させたい場合、 props.theme がセットされないためundefinedになりJavaScriptエラーが発生してしまいます。その場合ReactのdefaultPropsを利用するとよいと思います。

// Button.js

import React from "react";
import styled from "styled-components";
import { defaultTheme } from "./defaultTheme"

const StyledButton = styled.button`
  background-color: ${props => props.theme.button.backgroundColor[props.color]};
`;
StyledButton.defaultProps = {theme: defaultTheme}
 
export default function Button({ color, children }) {
  return <StyledButton color={color}>{children}</StyledButton>;
}

これでThemeProviderを利用しない場合でもdefaultPropsによりデフォルトのテーマ設定で props.theme が渡された状態になります。

CSSの詳細度の戦いになりにくい

コンポーネント内でCSSを設定していた場合、そのCSSを上書きするためには利用するコンポーネント側でCSSの詳細度を考えながら書かなければなりません。ときには !important を使わないといけない悲しい場面もあるかもしれません。

ThemeProviderを利用した場合、セットするthemeはJSのobjectなのでobject自体を上書きして渡してしまえば詳細度の戦いになりません。styled-componentsの仕様として、CSSの値にundefinedを渡せばプロパティ自体出力されなくなるので、無駄なCSSが出力されないというメリットもあります。

TypeScriptでも書ける

https://github.com/styled-components/styled-components/issues/1589#issuecomment-456641381 を参考にtheme props型をつけることができます。@types/styled-components の実装でDefaultThemeのinterfaceを拡張するとthemeに型をつけることができるようになっています。テーマ管理しているobjectのtypeofをとり以下のようにDefaultTheme interfaceに適用します。

import { defaultTheme } from "path/to/defaultTheme";

type ITheme = typeof defaultTheme;

declare module "styled-components" {
  interface DefaultTheme extends ITheme {}
}

少しロジックが入ったりする場合は、styled-componentsのテンプレート文字列全体を関数にして最後にcssを使って文字列を返すような実装もできます。

import React from "react";
import styled, { css } from "styled-components";
import { defaultTheme } from "./defaultTheme"

type Props = {
  color?: "default" | "primary" | "warning";
  disabled?: boolean;
  children: React.ReactNode;
}

const StyledButton = styled.button<Props>`
  ${({theme, color, disabled}) => {
    let backgroundColor = disabled ? "darkgray" : theme.button.backgroundColor[color];
    return css`
      background-color: ${backgroundColor};
    `
  }}
`;
StyledButton.defaultProps = {theme: defaultTheme}


export default function Button(props: Props): React.ReactElement {
  return <StyledButton{...props} />;
}

最後に

デフォルトのテーマ設定objectを別のobjectに切り替えるだけで、ユーザーごとのテーマ切り替えやダークテーマの対応がしやすかったりするメリットもあると思います。

直感的なCSSファイルでの実装ではないため、デザイナーがコードまで修正しているチームの場合修正しにくいデメリットはあると思いますが、エンジニアが管理しているチームでは管理しやすくなると思いますのでぜひ試してみてください。

参照リンク