サービス業で働く社内SEのブログ

技術メモを適当にかいつまんで記載します

小売業を退職してサービス業で働く社内SEになりました。

12月末日で9年9ヶ月という長い間勤めた会社を退職する事となりました。
現在有給消化中です。
在職期間中、皆様には大変お世話になりました。
で、次のステップは色々と迷ったのですが同じ情報システム部門で働く決意をしました。
せっかくなので約10年間でどんな仕事をしてきたのかをまとめてみたいと思います。

大学4年生

小売業ですので店舗がメインになります。
そこで大学4年生の後半から就職先の店舗でアルバイトとして働きはじめました。
当時、それまでのアルバイト(清掃や飲食等)が時給700円&深夜手当無しとかで働いていたのが、うちの店舗だと時給800円で、深夜は1000円ほど貰えており、感動した記憶があります。
しかも残業含めて全額支給。これがホワイト企業かと学生ながらに思いました。
ただ社員は結構厳しそうでした。
24時間店舗での勤務だったのですが社員が店長1人しかおらず、何かあったら常に呼び出される覚悟で戦っているのを覚えています。
元々私はシステム開発の部署(当時)に配属予定だったので、入社後、よっぽど一人で全部って事はないだろうなと思っていました。

新卒1年目

MAした事業の店舗に数ヶ月間の研修配属になりました。
アミューズ系の店舗とインターネットカフェ系の店舗です。
で、これが当時死ぬほど嫌でした。
実際勤務してみると楽しいものなのですが、スムーズにシステム開発の業務に携われるように大学時代から研修に入ってますし、更にこれ以上何を研修するんだと…。
大学の同級生はニコニコ笑顔で自分のやりたい技術系の仕事が出来ている中おいて行かれてる感が半端なくてストレスマックスだった記憶があります。
今となっては良い思い出ですし、良い経験をさせて頂いたと思っているのですが、新卒に研修と称して配属部署とは別の部署に行かせるの、個人的には大反対です。
明らかにモチベーションが下がるので、数年はちゃんと配属部署にまわしてあげた方が良いと思います。
その後、晴れてシステム開発の部署に配属され、主にスポーツクラブ系の顧客管理システムの開発保守運用に携わらせて頂けました。
とにかく色んな事が吸収できた一年だったと思います。
はじめてのお仕事プログラミングや、SQLだけでなくOracleを一から構築してDBA的な事もさせて頂いたり。
店舗がオープンする際は現地に赴いてキッティングしたり、MAした事業だったので顧客情報や各種データの移行であったりと、それなりに楽しかった記憶があります。
RAID構成であったり、サーバにはUPSなる装置がいるのだとか、イントラ構成、VPN構成だったりとにかく覚えることだらけでした。
ただ会社環境が結構閉ざされた状態にあったせいか、今となっては悪いコードの書き方を覚えたりしていました。

2年目~4年目

今から思い返すとかなり暗黒期でした。
元々の直属上司が病気で離脱してしまい、新たに中途で大量獲得した人たちが色々私の上に入ったのですが、基本的な考え方がまるで合わず衝突しました。
社員は使い捨てが基本で教育はしない。
上流は自分たちが考えて、下流は常駐してもらう協力会社さんに全てやらせる。使える社員がいれば使うしそれ以外は雑用をお任せ的な方針でした
この時、直属の上司にもその上にも「ちゃんと社員を教育して技術力の囲い込みと内製化を図った方が良い。このままだと今いる若い世代に何も残らないし戦力になっていかない」と直訴したのですが受け入れてもらえず、一度目の転職活動をはじめます。
無事内定も貰えて辞める直前まで行ったのですが、転機が訪れてお断りする事になりました。
ただ、今思えば私の意見が受け入れて貰えなかったのは良かった気がしています。
幾つかの要因がありますが、会社は勉強手段や教育を提供して当たり前という考えが多分根底にあったからこその発言だったと思います。
この頃からいざという時に会社は守ってくれないという思いが強まり、独学で自己啓発をするようになりました。
多分、人生25年間で自ら進んで勉強したのははじめてです(笑)
この自己啓発は今日まで継続的に続けています。
やっていた仕事内容は「システムのバグを直す予算はとれないけど現場が困るからデータだけ直して辻褄合わせてね」という名のデータ強制変更係でした。
しかもずっと一人で。
アルバイト時代の店長を思い出しました。
よく数年間続けた当時の私。

5年目

ここが人生の転機でした。
また直属の上司が変わったのですが、この上司が完全に技術者思考で、社員を育てると宣言し、当時私の実力はハナクソ以下だったにも関わらず最重要なプロジェクトに参画させて頂き、かなり勉強させて頂きました。
(ただ部下の教育に関しては、当時それしか選択肢がなくて若干仕方なく的な事を後から聞きましたが...笑)
もちろん聞けば教えてくれましたが、どちらかというとサッサと仕事進めていくから目で見て盗め的なスタイルだったと思います。
もう盗めるものは全て盗みました。細かいレベルだとメモのとり方とかそういうレベルで盗んでた気がします。
で、圧倒的に自宅で勉強したのもこのくらいの時期です。
毎日夜中の2時までひたすらコード書いてました。
この年になるまで、お恥ずかしい話ながら、.NETやってるとはいえ「interface知りません。」「overrideて何ですか。」「staticの意味って?」とかそういうレベルでした。
そういった基本的な事から、OOPデザパタMVCPDSに関するアーキテクチャ、細かい話までいくとIL化した後の挙動などなど死に物狂いで勉強しました。
上司が、技術もマネジメントもできるフルスタック的な感じの人だったので憧れとかあったんだと思います。
少なくとも技術だけは追いつこうと思ってやってました。

6年目~8年目

お陰様でいろいろなプロジェクトのPMを任されるようになりました。
それなりに炎上案件が多めでしたが、ものすごく楽しかったです。
VB→.NETへのリプレース案件が何故か多かった気がします。
中でもシステムの全面AWS移行に関するPMは非常に勉強になりました。
多分うちの会社は、就職氷河期世代くらいまでであれば誰でも1回は利用した事ある気がしますし、規模もそれなりに大きかったのですが、準備含め5ヶ月で終わらせました。
このあたりから技術力だけでなく、マネジメントも上司から勉強させて頂きました。
ただ同じようにやってはみたもののイマイチしっくりこなくて、ここに関しては自分なりのやり方の方が合うかなと思い試行錯誤しながら色々やり方を変えてきました。
その中で時には結構きつい口調で課員に言ってしまったりと、今思えば申し訳ない事もやってきたと思います。 最初はガチガチに管理するタイプでした。製造で言えば誰がどのクラスを作っていてどこに困っててとかほぼ全て管理していたのですが、部下から「窮屈だ」と指摘を受け、徐々に任せるスタイルに変えていきました。
何となくマネジメントに関しても、まだ入り口ですが掴んだ気がします。

9年目

おかげさまで評価して頂き管理職になりました。
そして退職しました(笑)
9年目は特に何もやっていなかった気がしています。
管理業務と役員や他部門との折衝が主な仕事でした。

退職理由

色々な理由が積み重なった結果として転職する運びとなったのですが大きかった点をいくつかあげてみます。

単純に小売業に飽きてしまった

これが最大の理由です。
私が情報システム部門にこだわっている理由として、システムを使って事業を拡大していく、業務を手助けするという点に面白みを見出していたのですが、事業自体に少し飽きてしまいました。
小売ですから基幹収益は当然店舗が大半を叩き出している分、予算や投資も店舗への比重がかなり多く、システムの優先順位もお店主体でやっていく事になります。
長い間携わってきたため、小売系の何のシステムを作るにしても、企画の段階からある程度先が見えてしまっていて面白みが薄れてきている事が原因です。
という事で、小売業以外の別の業種で新たにやってみたいという思いが強かったです。

色々とややこしい計上問題

ここはそんなに大した理由でもないのですが、店舗って売上の計上が難しいんですよ。
当然店舗で売り上げた物は店舗に計上されるのですが、例えばECで売り上げた場合、販管費の一部は間接部門が持っていたり、他店舗がもっていたりします。
そこで売上はどこにつけるのかという問題が出てきます。
誤解のないようになのですが、これは一例に出しただけであって、否定しているわけではありません。
店舗運営する際のモチベーションに関わるので至極まっとうな話ですし、またこういう話をしなくてすむように適切な評価制度を会社が導入してあげるべき事案です。
ただ、こういう本来会社にとって攻めに繋がりにくいシステムを作る事に違和感を感じていたのも事実です。

それは手でやっとけと言える雰囲気作り

をやってきたつもりです。
極力無駄なシステムは作らないし提供しない。
これも誤解のないようにですが、必要な物はちゃんと作るべきであって、ユーザ部門から言われた事をそのままやるだけの社内SEがイケないです。
費用対効果(だけで見るのは危険ですが)も鑑みて最終的な判断を出来る限り的確にやりました。
ただこれも絶妙な力加減が必要で、全部お断りしちゃうと仕事が進まないんですよね。
「情シスはあいつが門前払いするから情報共有しなくていいよ」なんて噂が立つとこれまた仕事がやりにくい。
ので、妥協できる範囲で最大限やってきたつもりです。
ただ、これがピラミッドの頂点から来るとどうしようもない。
「下から上は変えれない」とまでは言いませんが、もう正直に書くと「下から上を変える為には多大な労力と時間を要する」という感じです。
私が管理職に就任して断ろうとした案件の一発目がこれでした。
こういうのが連発してくると、私の考えと会社の考えがズレてきてる気がしてなりませんでした。

なんとなく感じる不信感

立場が上になればなるほどやっぱり感じるわけです。そういうの。
最終出勤日以降もサイレントでやってくれました。
具体的には伏せますが、何故飛ぶ鳥跡を濁さずが出来ないのでしょうか。
本当に残念です。

お給料

最終的なお給料ですが700万円でした。
管理職以上は残業代がつきませんでしたのと、今期から年俸制に変わり賞与もなくなったので完全に上記金額の12分割でした。
小売という業界である点、地元近くでの職場、年齢が31歳という事を考えると恐らくそれなりに良い待遇だったと思います。

転職のきっかけ

所謂ヘッドハンティングというやつです。
超かっこよく書きました!これが書きたかったんですよ!(実際は誘われて入っただけ)
ただ、誘われながらも他の会社も見たかったので、リクナビNEXTDODA、ビズリーチあたりに登録しました。
が、あまり私に合う求人がなく、使いこなせませんでした
他には個人的に相談にのって頂いたエージェントさんには色々ご紹介して頂いたりしたもののマッチしませんでした。
一応「商社」になるのですが、何となく商社って感じがしないのと、「サービス業」っぽい雰囲気があるのでそう思う事にしました(笑)
年間休日やお給料もかなり上げてもらいまして、本当にありがたい限りです。

最後に

次の職場は、本社がちょっと意外なところにありますが、勤務地は前の会社と距離がほぼ変わらないです。
システムがほぼ何もない会社と聞いているので、「事業を大きくするためには」を常に考えながら貢献していこうと思います。
少なくとも初めての中途社員を経験するということで、即戦力が求められます。
対価にみあった働きができるよう頑張っていきたいです。
最後まで読んでいただいて、ありがとうございました。

【C#】Slackにアップしたファイルを自動削除するAWSのLambdaファンクション

元々会社で私管轄のところはSlackでコミニュケーションを取っていたのですが
ついに他とも合同で使うことになりました。
で、今まではSlackにアップしていたファイルは適当なタイミングで手動削除していたのですが流石に人数が増えてきたので自動削除されないと厳しくなり先日GAされたC#AWS Lambdaでさっそく作ってみました。

VisualStudioにAWS Lambdaの環境を構築する手順は下記ブログあたりを参照してください。
VS2015推奨です。
VS2013以下だとSDK入れてもプロジェクトテンプレートが出てこないのでS3かZIPで直接Lambdaにアップする必要があります

qiita.com

SlackのAPIリファレンスはここにあります。
サイト上でテストできるので非常に分かり易いです。
api.slack.com

で、メインのコードは以下な感じです。
AWSのシリアライザーもありそうでしたが、普通にJson.NET使いました。
stringを返していますが、テンプレートから作った初期クラスがそうなっていただけでデバッグ時以外特に意味はないです。

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Newtonsoft.Json;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializerAttribute(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace SlackDeleteFileFunction
{
    public class Function
    {
        /// <summary>
        /// Slackにアップされている1週間以上前のファイルを削除します
        /// </summary>
        /// <returns></returns>
        public string FunctionHandler()
        {
            try
            {
                return SlackFileDeleteAsync().Result;
            }
            catch (Exception e)
            {
                return e.ToString();
            }
        }

        /// <summary>
        /// ファイル削除のメイン処理
        /// </summary>
        /// <returns></returns>
        public async Task<string> SlackFileDeleteAsync()
        {
            var ret = string.Empty;

            // SlackのAPIトークン
            const string token = @"ここにトークンを記載";

            using (var httpClient = new HttpClient())
            {
                using (var response = await httpClient.GetAsync(string.Format(@"http://slack.com/api/files.list?token={0}&pretty=1", token)))
                {
                    var jsonData = JsonConvert.DeserializeObject<SlackData>(response.Content.ReadAsStringAsync().Result);
                    ret = string.Format(@"アップされているファイル数:{0}件", jsonData.Files.Count);

                    var count = 0;
                    foreach (var file in jsonData.Files)
                    {
                        if (FromUnixTime(file.Created) < DateTime.Now.AddDays(-7))
                        {
                            await httpClient.GetAsync(
                                string.Format(@"http://slack.com/api/files.delete?token={0}&file={1}&pretty=1", 
                                token,
                                file.Id));
                            count += 1;
                        }
                    }
                    ret += Environment.NewLine;
                    ret += string.Format(@"削除したファイル数:{0}件", count);
                }
            }

            return ret;
        }

        /// <summary>
        /// UnixEpochをDatetimeで表した定数
        /// </summary>
        public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        /// <summary>
        /// Unix時間からDatetimeに変換するメソッド
        /// </summary>
        /// <param name="unixTime"></param>
        /// <returns></returns>
        public static DateTime FromUnixTime(long unixTime)
        {
            return UnixEpoch.AddSeconds(unixTime);
        }

    }
}

Jsonをデシリアライズするので入れ子のクラスも以下のように準備

using System.Collections.Generic;

namespace SlackDeleteFileFunction
{
    /// <summary>
    /// Slackのデータクラス
    /// </summary>
    public class SlackData
    {
        /// <summary>
        /// ファイルリスト
        /// </summary>
        public List<FileInfo> Files { get; set; }

        /// <summary>
        /// ファイル情報
        /// </summary>
        public class FileInfo
        {
            /// <summary>
            /// ファイルID
            /// </summary>
            public string Id { get; set; }

            /// <summary>
            /// ファイルがアップされた日時
            /// </summary>
            public long Created { get; set; }
        }
    }
}

AWSにデプロイした後はTriggerを決めます。
上記ソースは1週間以上前にアップされているファイルを定期的に削除してもらうのが望ましいため
CloudWatch Eventsを1日おきにスケジューリングします。

f:id:Einherjar1632:20161223193055p:plain

AWS LambdaがC#に対応したことにより、エンプラ界でもサーバレスアーキテクチャが取っ付きやすくなりました。
VB?知らない子ですね・・・

【C#】【AWS】 Lambda(C#)に対応した.NETSDK(Toolkit for Microsoft Visual Studio)をインストールするとエラーになる解決策

今のところですが
AWSToolsAndSDKForNet_sdk-3.3.27.0_ps-3.3.27.0_tk-1.11.0.0.msi
AWSToolsAndSDKForNet_sdk-3.3.28.0_ps-3.3.28.0_tk-1.11.0.0.msi
の2つ、会社PCと私の個人PCでエラーが発生することが判明しています。

msiインストール後、ROLLBACKが走りWindowsのイベントログを確認すると

AWS Tools for Windows -- Error 1722. There is a problem with this Windows Installer package. A program run as part of the setup did not finish as expected. Contact your support personnel or package vendor. Action vimC7F477E7C50742E0CB2CCFE51CE3E58C, location: C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\VSIXInstaller.exe, command: /q /skuName:Community /skuVersion:14.0 "C:\Program Files (x86)\AWS Tools\VSToolkit.Install\14.0\AWSToolkitPackage.vsix" /admin

的なエラーを吐いていると思います。

おそらく近日中にAWSが対応してくれると思いますが
問題なくインストールできるmsiがありますのでリンクを貼っておきます。

https://aws-net-sdk.s3.amazonaws.com/AWSToolsAndSDKForNet_sdk-3.3.26.0_ps-3.3.26.0_tk-1.10.0.6.msi

【C#】CsvHelperを使ってTSVファイルを読み込む

TSVファイルを読み込んでDBに突っ込むというバッチアプリを作りました。
その時に使ったライブラリのメモです。

基本的な使い方に関しては例のごとくかずきさんのブログを引用させていただきます。

blog.okazuki.jp

公式のページにもHowToが載っていますので、そちらを見るのもよいです。

joshclose.github.io

で、名前の如くCSVファイルを読み込んでクラスにマッピングする事を目的に
作られているライブラリですが、TSVももちろんいけちゃいます。
使い方も簡単で、DelimiterプロパティにTSV文字をセットするだけです。

using (var tsv = new CsvHelper.CsvReader(sr))
{
    // CsvHelperの設定。上から順に[ヘッダ有無の設定][区切り文字の設定][Mappingするクラスの設定]
    tsv.Configuration.HasHeaderRecord = false;
    tsv.Configuration.Delimiter = Constants.Delimiter.Tab;
    tsv.Configuration.RegisterClassMap<T1>();

    // Mapping処理
    var ret = tsv.GetRecords<T2>();
}

ちょっとExceptionの名前忘れちゃいましたが
マッピング中に例外が起きると、例外が起きた行/列のExceptionを返してくれますし
非常に使いやすいライブラリでした。

【Xamarin】Xamarin.FormsのNavigationPageでBarを消して(隠して)下からにゅっと出てくるやつ

今回、参考にさせて頂いた元ネタの記事は@ticktackmobileさんの記事です。

ticktack.hatenablog.jp

これを同様の手法でNavigationPageに実装に実装すると

f:id:Einherjar1632:20160220225045g:plain

NavigationPageの仕様上こんな感じでNavigationBarのところで止まります。
これを全画面表示にする場合はSetHasNavigationBar(Xamarin.Forms.BindableObject page, bool value)を使用すると良さそうです。

if (!TweetPage.IsVisible)
{
    NavigationPage.SetHasNavigationBar(this, false);
    await TweetPage.TranslateTo(0, TweetPage.Height, 0);
    TweetPage.IsVisible = !TweetPage.IsVisible;
    await TweetPage.TranslateTo(0, 0, 300);
}

f:id:Einherjar1632:20160220225452g:plain

それっぽく全画面表示で呼び出しが出来ました。

【Xamarin】Xamarin.FormsのMasterDetailPageインスタンス時にエラーが出る

XamarinFormsのMasterDetailPageをxamlで書いていたのですが

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ChapterZero.Views;assembly=ChapterZero"
             x:Class="ChapterZero.Views.MainPage">
  <MasterDetailPage.Master>
    <local:MasterPage></local:MasterPage>
  </MasterDetailPage.Master>
  <MasterDetailPage.Detail>
    <NavigationPage>
      <x:Arguments>
        <local:TimeLinePage />
      </x:Arguments>
    </NavigationPage>
  </MasterDetailPage.Detail>
</MasterDetailPage>

MasterDetailPageをこんな感じで書きつつ

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:ChapterZero.ViewModels;assembly=ChapterZero"
             x:Class="ChapterZero.Views.MasterPage"
             Padding="0,40,0,0">
  <ContentPage.BindingContext>
    <vm:MasterPageViewModel></vm:MasterPageViewModel>
  </ContentPage.BindingContext>
  <ContentPage.Content>
    <StackLayout VerticalOptions="FillAndExpand">
      <ListView VerticalOptions="FillAndExpand" ItemsSource="{Binding MasterPageList}">
        <ListView.ItemTemplate>
          <DataTemplate>
            <ImageCell Text="{Binding Title}" ImageSource="{Binding IconSource}" />
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>
  </ContentPage.Content>

</ContentPage>

MasterPageをこんな感じで書いたところ、以下のようなエラーが出ました。

f:id:Einherjar1632:20160214162836p:plain

System.Reflection.TargetInvocationExceptionとのことで
どうやらMasterPageに割り当てるContentPageのTitleを設定していなかった事が原因のようです。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:ChapterZero.ViewModels;assembly=ChapterZero"
             x:Class="ChapterZero.Views.MasterPage"
             Padding="0,40,0,0"
             Title="たいとる">
  <ContentPage.BindingContext>
    <vm:MasterPageViewModel></vm:MasterPageViewModel>
  </ContentPage.BindingContext>
  <ContentPage.Content>
    <StackLayout VerticalOptions="FillAndExpand">
      <ListView VerticalOptions="FillAndExpand" ItemsSource="{Binding MasterPageList}">
        <ListView.ItemTemplate>
          <DataTemplate>
            <ImageCell Text="{Binding Title}" ImageSource="{Binding IconSource}" />
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>
  </ContentPage.Content>

</ContentPage>

上記のように、Titleを設定したところインスタンスされました。
この事象、どなたかが何処かで書かれていた気がするのですがすっかり忘れていたのでメモがてら記載しておきます。

【AWS】Auroraでnot null制約が効かない場合

タイトル詐欺です。
AmazonのAuroraはMySQLと互換性があります。
このため、タイトル通りの挙動をした際はMySQLと同様に
AWSコンソール>RDS>パラメータグループ
の順に辿り「sql_mode」を「STRICT_ALL_TABLES」に変更する必要があります。

MySQLの場合はプルダウンが用意されていますが
Auroraはいまのところテキストエリアに直接書かないとダメなようです。