<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Tobby Kuo on Medium]]></title>
        <description><![CDATA[Stories by Tobby Kuo on Medium]]></description>
        <link>https://medium.com/@tobby168?source=rss-b9b41258e57b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*-6zLUEsL2si38bqvdHqQwg.jpeg</url>
            <title>Stories by Tobby Kuo on Medium</title>
            <link>https://medium.com/@tobby168?source=rss-b9b41258e57b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 01:02:53 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@tobby168/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Data Streaming in Practice (Kafka + Flink): Building a Flight Baggage Tracking System]]></title>
            <link>https://medium.com/@tobby168/data-streaming-in-practice-kafka-flink-building-a-flight-baggage-tracking-system-b972b3d1fc04?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/b972b3d1fc04</guid>
            <category><![CDATA[apache-kafka]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[k8s]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Tue, 20 Jan 2026 05:52:38 GMT</pubDate>
            <atom:updated>2026-01-20T05:52:38.171Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Why choose “baggage tracking” as a streaming topic?</strong></h3><p>That day while waiting at the gate for a connection, I had a random thought and asked ChatGPT:</p><p>“After bags are checked in, is the process like the scene in Toy Story?”</p><p>I’ve always loved that scene — once bags go into the conveyor belts, it’s like another world: complex tracks, humans and machines working together, and eventually the bags arrive at the right aircraft — all within the 1–2 hours while we’re shopping at duty free.</p><p>After a few rounds of discussion with ChatGPT, I realized that <strong>baggage tracking</strong> is a textbook example of <strong>IoT + Data Streaming + real‑time dashboarding/alerting</strong>.</p><p>Streaming data has always been my home turf — I worked for two years on the Ads Audience Infra team at Microsoft. The example I always give friends/family: after you search for a product, how fast do you start seeing related ads? That “real‑time feedback” is highly tied to streaming data and recommendation systems.</p><p>So I decided to build this end‑to‑end toy project. When people learn Apache Flink, the example projects are usually the same few over and over. Baggage tracking felt like a realistic, intuitive scenario that’s also great for demoing.</p><h3><strong>Why it’s easy to understand — and hard to build well</strong></h3><p>Baggage tracking is straightforward conceptually, but tough to build end‑to‑end because it contains all the classic streaming challenges:</p><p>- <strong>High event volume and continuity</strong>: each bag gets scanned multiple times.</p><p>- <strong>Ordered events, but order is not guaranteed</strong>: late arrival, retries, delays are real.</p><p>- <strong>You care about <em>state</em>, not just events</strong>: the question is “where is the bag now?”, not a log list.</p><p>- <strong>You still need KPIs</strong>: load percentage, missing percentage, carousel arrival rate.</p><p>- <strong>You need traceability</strong>: when something goes wrong, you need full history.</p><p>Show me the code: <a href="https://github.com/tobby168/Streaming-data-to-track-flight-bags">https://github.com/tobby168/Streaming-data-to-track-flight-bags</a></p><p><strong>Note</strong>: This project was built entirely using Codex CLI vibe coding. It took me about 4–5 evenings and I never opened an IDE. This article was also written the same way — so it might sound a little “robotic.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A0WYeeeCcZslxmAngZXqFw.png" /><figcaption><strong>Select a flight ID → see all related bags and their states</strong></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oaRrI33ks6cZbSxFZYx0IA.png" /><figcaption><strong>Select a customer ID → see all related bags and their states</strong></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nawfYfYpNrVsTQCQv3pOFA.png" /><figcaption><strong>Select a bag ID → see full history</strong></figcaption></figure><h3><strong>Core Challenges</strong></h3><p>1. High-throughput event stream</p><p>2. Out-of-order events + latest-state consistency</p><p>3. Multi-level state (Bag / Itinerary / Flight)</p><p>4. Query performance</p><h3><strong>Architecture Design</strong></h3><pre>Producer → Kafka (baggage.events.v1) → Flink Jobs → ClickHouse → Grafana</pre><p><strong><em>Why these components?</em></strong></p><p>- <strong>Kafka</strong>: A distributed streaming platform and industry standard; high horizontal scalability and durable storage. On‑disk logs + offsets give strong fault tolerance.</p><p>- <strong>Flink</strong>: Supports stateless and stateful stream processing; you can write SQL or functional code.</p><p>- <strong>ClickHouse</strong>: OLAP with second‑level query latency.</p><p>- <strong>Grafana</strong>: Real‑time visualization; dashboards are highly flexible and can be managed as code.</p><h3><strong>Design Principles</strong></h3><p><strong><em>### Principle A: Immutable Event Log</em></strong></p><p>Events are facts and shouldn’t be rewritten.</p><p>So Kafka / ClickHouse bag_events always keep the raw events.</p><p><strong><em>### Principle B: Deterministic State</em></strong></p><p>Bag / Itinerary / Flight states should always be re‑derivable from events.</p><p>So state is computed with clear SQL rules, not a black box.</p><p><strong><em>### Principle C: Write Fast, Read Faster</em></strong></p><p>Ingestion must be high-throughput, but query latency must be fast.</p><p>That’s why we design query‑friendly tables like bag_latest / flight_kpis.</p><p><strong><em>### Principle D: Layered State</em></strong></p><p>Different layers use different jobs; don’t cram everything into one table.</p><h3><strong>Producer: How baggage events are simulated</strong></h3><p>The producer simplifies countless IoT edge devices into a single Python process. It fetches all TPE flight schedules over the next <strong>three days</strong>, and simulates events:</p><p>- Each flight has <strong>random 100–300 passengers</strong></p><p>- Each passenger has <strong>1–3 bags</strong> (normal distribution, mostly 2)</p><p>- Check‑in time also follows a <strong>normal distribution</strong></p><p>- It tracks each bag’s state in memory to avoid impossible sequences (e.g. carousel arrival before check‑in)</p><p><strong>Event Model</strong></p><pre>{<br>&quot;event_id&quot;: &quot;fae23be-f02f-44a6-a102–6b089b48508d&quot;,<br>&quot;bag_id&quot;: &quot;BAG0000476&quot;,<br>&quot;itinerary_id&quot;: &quot;ITI00000343&quot;,<br>&quot;customer_id&quot;: &quot;CUST00000343&quot;,<br>&quot;flight_id&quot;: &quot;UA852|2026–01–16|TPE-&gt;SFO&quot;,<br>&quot;leg_index&quot;: 0,<br>&quot;airport&quot;: &quot;TPE&quot;,<br>&quot;event_type&quot;: &quot;CheckedIn&quot;,<br>&quot;event_time&quot;: &quot;2026–01–16T02:30:00+00:00&quot;,<br>&quot;ingest_time&quot;: &quot;2026–01–16T02:30:18+00:00&quot;,<br>&quot;scan_point_id&quot;: &quot;TPE_CHECKEDIN_9&quot;,<br>&quot;reader_type&quot;: &quot;Barcode&quot;,<br>&quot;confidence&quot;: 0.977,<br>&quot;attributes&quot;: {<br>  &quot;dest&quot;: &quot;SFO&quot;,<br>  &quot;terminal&quot;: &quot;2&quot;,<br>  &quot;gate&quot;: &quot;9&quot;,<br>  &quot;aircraft&quot;: &quot;B772&quot;<br>  }<br>}</pre><h3><strong>Kafka: the event database (in the simplest sense)</strong></h3><p>In the simplest terms, Kafka is a database for event streaming.</p><p>It accepts data via a stream, and downstream systems can read it in order.</p><p>In this project:</p><p>- Producer writes baggage events to Kafka</p><p>- Flink reads Kafka, computes state, then writes to other topics</p><p>- ClickHouse listens to Kafka and ingests data into tables</p><p>Why not write directly to a database?</p><p>Because streaming systems require <strong>replayability, fan‑out, and decoupling</strong>. Kafka is the “source of truth” event log — if anything fails, we can replay and rebuild.</p><p>We also added <strong>Kafka UI</strong> for local debugging and live data inspection.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RoER7F65gjtWfZnOu4Oo6Q.png" /></figure><p><strong>Topic List</strong></p><p><strong><em>📌 Events &amp; State</em></strong></p><p>- <strong>baggage.events.v1</strong>: raw event stream (Retention), everything starts here</p><p>- <strong>baggage.bag.latest.v1</strong>: latest state per bag (Compacted), written by BagLatestJob</p><p><strong><em>📌 KPIs &amp; Alerts</em></strong></p><p>- <strong>baggage.flight.kpis.v1</strong>: flight KPIs from FlightKpiAlertJob</p><p>- <strong>baggage.alerts.v1</strong>: alerts when missing/freshness exceeds thresholds</p><p><strong><em>📌 Journey State</em></strong></p><p>- <strong>baggage.notifications.v1</strong>: itinerary status notifications (AT_RISK / DELAYED / EXCEPTION)</p><p><strong><em>📌 Base Data</em></strong></p><p>- <strong>flight.schedule.v1</strong>: flight schedule (Compacted)</p><p>- <strong>baggage.assignment.v1</strong>: baggage assignment (Compacted)</p><h3><strong>Flink Jobs: the brain of the system</strong></h3><p>Once streaming data enters, we transform <strong>events</strong> into <strong>state</strong> and <strong>metrics</strong>.</p><p>There are three jobs, each for a different layer: Bag / Itinerary / Flight.</p><p><strong>1) BagLatestJob: latest state per bag</strong></p><p>We want to answer: <strong>“Where is this bag right now?”</strong></p><p>Each bag has 6–8 events, so we can’t scan raw events every time.</p><p>We use a SQL window to get the latest event:</p><pre>SELECT bag_id, itinerary_id, customer_id, flight_id, leg_index, airport,<br>event_type, event_time_ts AS event_time, ingest_time_ts AS ingest_time, attributes<br>FROM (<br>SELECT e.*,<br>ROW_NUMBER() OVER(<br>PARTITION BY bag_id<br>ORDER BY event_time_ts DESC, ingest_time_ts DESC<br>) AS rownum<br>FROM baggage_events_view e<br>)<br>WHERE rownum = 1</pre><p><strong>Outputs</strong></p><p>- Kafka baggage.bag.latest.v1 (compacted)</p><p>- ClickHouse baggage.bag_latest</p><p><strong>Trade‑offs</strong></p><p>- ✅ Fast query</p><p>- ✅ Only latest state</p><p>- ⚠ Full history still requires bag_events</p><p><strong>2) ItineraryStatusJob: itinerary-level state</strong></p><p>A single bag’s state doesn’t reflect the full trip.</p><p>We need the <strong>journey state</strong> for each itinerary.</p><pre>SELECT<br>itinerary_id,<br>CASE<br>WHEN event_type = &#39;TransferIn&#39; THEN &#39;AT_RISK_FOR_CONNECTION&#39;<br>WHEN event_type = &#39;ReassociatedToNextLeg&#39; THEN &#39;RECOVERING&#39;<br>WHEN event_type = &#39;LoadedOnAircraft&#39; THEN &#39;ON_TIME&#39;<br>WHEN event_type = &#39;Offloaded&#39; THEN &#39;DELAYED_OR_REROUTED&#39;<br>ELSE &#39;EXCEPTION&#39;<br>END AS journey_state,<br>event_type AS last_event_type,<br>airport AS last_airport,<br>event_time_ts AS last_event_time,<br>ingest_time_ts AS updated_at<br>FROM ranked_events<br>WHERE rn = 1</pre><p><strong>Outputs</strong></p><p>- Kafka baggage.notifications.v1</p><p>- ClickHouse itinerary_status</p><p><strong>Trade‑off</strong></p><p>- ✅ One status per itinerary</p><p>- ⚠ Rules are simplified (not a full airline workflow)</p><p><strong>3) FlightKpiAlertJob: flight KPIs &amp; alerts</strong></p><p>Operations care about flights, not single bags.</p><p>We aggregate in 10‑second windows:</p><pre>SELECT<br>flight_id,<br>window_start,<br>window_end,<br>COUNT(*) AS events,<br>SUM(CASE WHEN event_type = &#39;LoadedOnAircraft&#39; THEN 1 ELSE 0 END) AS loaded,<br>SUM(CASE WHEN event_type = &#39;LoadedOnAircraft&#39; THEN 0 ELSE 1 END) AS missing,<br>TIMESTAMPDIFF(SECOND, MAX(event_time_ts), CURRENT_TIMESTAMP) AS freshness_sec<br>FROM TABLE(<br>TUMBLE(TABLE events_with_proc_time, DESCRIPTOR(proc_time), INTERVAL &#39;10&#39; SECOND)<br>)<br>GROUP BY flight_id, window_start, window_end<br>### Severity<br>CASE<br>WHEN missing &gt; 0 AND freshness_sec &gt; 120 THEN &#39;CRITICAL&#39;<br>WHEN missing &gt; 0 THEN &#39;WARN&#39;<br>ELSE &#39;OK&#39;<br>END</pre><p><strong>Outputs</strong></p><p>- Kafka baggage.flight.kpis.v1</p><p>- Kafka baggage.alerts.v1</p><p>- ClickHouse flight_kpis</p><p><strong>Trade‑off</strong></p><p>- ✅ KPI is near‑real‑time</p><p>- ✅ Easy to alert</p><p>- ⚠ 10s window is demo‑size; production can increase</p><h3><strong>ClickHouse: the visualization data hub</strong></h3><p>Kafka + Flink are essential, but ClickHouse is the component with the most alternatives.</p><p>For visualization, Grafana simply reflects its data source — if the database is slow, dashboards are slow.</p><p>So I prioritized the storage layer, and ClickHouse’s speed was the best fit.</p><p>ClickHouse is a <strong>distributed, column‑based OLAP database</strong>.</p><p>- Distributed → scales horizontally</p><p>- Columnar → fast analytics</p><p>- OLAP → optimized for aggregates, not single‑row CRUD</p><p>Kafka → ClickHouse ingestion is done via <strong>Kafka tables + Materialized Views</strong>, so there’s no separate ETL pipeline.</p><p><strong>Tables</strong></p><p>- bag_events: raw events</p><p>- bag_latest: latest state</p><p>- itinerary_status: journey state</p><p>- flight_kpis: flight KPIs</p><h3><strong>Grafana Dashboards</strong></h3><p>- Flight Bag Status</p><p>- Passenger Bag Status</p><p>- Fleet Coverage</p><p>- Bag Detail</p><p>- Itinerary Exceptions</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qwnYTWsK_xdDwcbk1zoskg.png" /></figure><h3><strong>Kubernetes: cluster management</strong></h3><p>Even though the architecture diagram doesn’t show it, the whole project is containerized and runs on Kubernetes.</p><p>If you only want a local demo, K8s isn’t required — but I wanted to show <strong>how the system would look in a production‑style setup</strong>, because that’s often missing from learning materials.</p><p><strong>Why K8s?</strong></p><p>- Each component can be deployed independently</p><p>- Restart / rollout support</p><p>- Closer to real production environments (Kafka/Flink/ClickHouse)</p><p>In practice:</p><p>- K8s is cloud‑native and works well with Azure / AWS / GCP</p><p>- Scaling Kafka/Flink is as simple as changing K8s config</p><p>- Everything is Infrastructure‑as‑Code (producer config, ClickHouse DDL, Grafana dashboards)</p><p>- Components are decoupled — you could swap Flink with Spark Streaming or ClickHouse with another OLAP engine as long as Kafka topics are stable.</p><h3><strong>Conclusion</strong></h3><p>One reason I picked this project is because there are plenty of tutorials on Kafka, Flink, K8s, ClickHouse, and Grafana — but very few that explain a <strong>complete system</strong> end‑to‑end.</p><p>The point of this project isn’t just to create a pretty dashboard.</p><p>It’s to walk through the full pipeline: <strong>event model → state machine → KPIs → OLAP queries</strong>.</p><p>Once you understand this structure, you can apply it to IoT, financial transactions, logistics tracking, or even recommendation systems.</p><p>Of course, a side project is still far from true production.</p><p>I remember from my streaming‑data days that just optimizing Kafka cluster usage or implementing streaming semantics and disaster recovery could each take months.</p><p>But hopefully this gives streaming beginners a clear direction — and a taste of what makes this field so fascinating: <strong>the ability to process data in seconds or even sub‑seconds</strong>, something that’s hard to imagine in</p><p>the world of batch processing.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b972b3d1fc04" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Data Streaming流處理(Kafka+Flink)實作航班行李追蹤]]></title>
            <link>https://medium.com/@tobby168/data-streaming%E6%B5%81%E8%99%95%E7%90%86-kafka-flink-%E5%AF%A6%E4%BD%9C%E8%88%AA%E7%8F%AD%E8%A1%8C%E6%9D%8E%E8%BF%BD%E8%B9%A4-b06874438f1b?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/b06874438f1b</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[streaming]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[k8s]]></category>
            <category><![CDATA[grafana]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Tue, 20 Jan 2026 05:40:38 GMT</pubDate>
            <atom:updated>2026-01-20T05:41:12.113Z</atom:updated>
            <content:encoded><![CDATA[<h3>前言：為什麼選定「行李追蹤」作為流處理題目</h3><p>那天在登機門等轉機的時候，腦中一閃問了ChatGPT「行李 checked-in 後，流程就像玩具總動員裡面一樣嗎」，一直很喜歡玩具總動員裡面的那個場景，行李被輸送帶送進去後，像是一個完全不一樣的世界，軌道錯綜復雜、人類與機器各種合作最終把行李送到該去的機艙裡，而且一切都在我們逛免稅店的一兩個小時內完成。</p><p>經過幾輪跟ChatGPT的探討，發現「行李追蹤」這個題目其實就是非常典型的 IOT + Data Streaming + Real-time dashboarding/alerting 場景。剛好 Data Streaming 就是我的老本行，之前在微軟 Ads Audience Infra team 做了兩年的 data streaming （我最常跟家人朋友舉的例子：當你搜了一個商品後，多久以內開始看到這個商品相關的廣告，這種即時資料跟推薦演算法就是跟 streaming data 高度相關的），於是就有了做這個 end-to-end toy project 的想法，畢竟之前學 <a href="https://nightlies.apache.org/flink/flink-docs-release-2.2/docs/learn-flink/overview/">Apache Flink</a> 的時候，通常舉的例子都是那幾個，我覺得行李追蹤是個相當貼近現實的場景，也很適合拿來展示。</p><p>行李追蹤這件事很容易懂，但非常不容易做完整。因為它同時包含串流系統最常見的難題：</p><p>- <strong>事件多且連續</strong>：每顆行李一路要被掃好幾次。</p><p>- <strong>事件有順序但順序不保證</strong>：晚到、重送、延遲都會發生。</p><p>- <strong>你要的不是事件，而是「狀態」</strong>：關心的是「現在在哪」，不是看 log。</p><p>- <strong>你還要 KPI</strong>：航班裝載率、缺件率、到轉盤率。</p><p>- <strong>還要能回溯</strong>：發生問題要完整事件歷程。</p><p>講這麼多，show me the code: <a href="https://github.com/tobby168/Streaming-data-to-track-flight-bags">https://github.com/tobby168/Streaming-data-to-track-flight-bags</a></p><p>註：實作方面全程用 Codex CLI vibe coding，總共花費四五個晚上完成，連IDE都沒有打開。當然本篇文章也是，所以可能機器人感會有點重。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A0WYeeeCcZslxmAngZXqFw.png" /><figcaption>選定航班號，展示所有關聯行李的狀態</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oaRrI33ks6cZbSxFZYx0IA.png" /><figcaption>選定顧客id，展示所有關聯行李的狀態</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nawfYfYpNrVsTQCQv3pOFA.png" /><figcaption>選定行李id，展示所有歷史紀錄</figcaption></figure><h3><strong>核心困難點（Challenges）</strong></h3><p>1. <strong>高吞吐事件流</strong></p><p>2. <strong>亂序事件 + 最新狀態一致性</strong></p><p>3. <strong>多層級狀態（Bag / Itinerary / Flight）</strong></p><p>4. <strong>查詢效能</strong></p><h3><strong>架構設計</strong></h3><pre>Producer → Kafka (baggage.events.v1) → Flink Jobs → ClickHouse → Grafana</pre><p><strong><em>為什麼選這些元件？</em></strong></p><p>- <strong>Kafka</strong>：分散式流資料平台，幾乎是業界標準，非常高的水平擴展性跟資料持久化，on disk files 跟 offsets 機制可以提高很高的容錯性</p><p>- <strong>Flink</strong>：支援 Stateless, Stateful 流處理，可以寫 SQL 也可以寫 functional programming</p><p>- <strong>ClickHouse</strong>：OLAP 秒級查詢</p><p>- <strong>Grafana</strong>：即時可視化，Dashboard彈性高可以用程式碼管理</p><h3><strong>設計原則</strong></h3><p><strong><em>### 原則 A：事件不可變（Immutable Log）</em></strong></p><p>事件是事實，不應該改寫。</p><p>所以 Kafka / ClickHouse bag_events 永遠保留事件。</p><p><strong><em>### 原則 B：狀態可重建（Deterministic State）</em></strong></p><p>Bag / Itinerary / Flight 狀態都應該能從事件重算。</p><p>所以狀態的計算都用 SQL 規則，不做黑盒。</p><p><strong><em>### 原則 C：寫快、讀更快</em></strong></p><p>寫入可以高吞吐，但查詢一定要快。</p><p>所以設計 bag_latest / flight_kpis 這類 query‑friendly 表。</p><p><strong><em>### 原則 D：分層狀態</em></strong></p><p>不同層級就用不同 job，不要塞在同一張表。</p><h3><strong>Producer：模擬行李事件的機制</strong></h3><p>Producer 把無數的 IOT (Edge) devices 簡化成一支簡單的 python 程式，事先抓取了桃機 TPE 三天內的所有航班資訊，模擬發出事件：每個航班隨機 100~300 人、每個人有 1–3 件行李（常態分布，大部分兩件）、check-in 時間也是常態分布、會在記憶體裡追蹤每個行李的狀態機以防發出不合理的事件（行李先進轉盤才 check-in 之類的）。</p><p><strong>Event Model</strong></p><pre>{<br>    &quot;event_id&quot;: &quot;fae23be-f02f-44a6-a102-6b089b48508d&quot;,<br>    &quot;bag_id&quot;: &quot;BAG0000476&quot;,<br>    &quot;itinerary_id&quot;: &quot;ITI00000343&quot;,<br>    &quot;customer_id&quot;: &quot;CUST00000343&quot;,<br>    &quot;flight_id&quot;: &quot;UA852|2026-01-16|TPE-&gt;SFO&quot;,<br>    &quot;leg_index&quot;: 0,<br>    &quot;airport&quot;: &quot;TPE&quot;,<br>    &quot;event_type&quot;: &quot;CheckedIn&quot;,<br>    &quot;event_time&quot;: &quot;2026-01-16T02:30:00+00:00&quot;,<br>    &quot;ingest_time&quot;: &quot;2026-01-16T02:30:18+00:00&quot;,<br>    &quot;scan_point_id&quot;: &quot;TPE_CHECKEDIN_9&quot;,<br>    &quot;reader_type&quot;: &quot;Barcode&quot;,<br>    &quot;confidence&quot;: 0.977,<br>    &quot;attributes&quot;: {<br>      &quot;dest&quot;: &quot;SFO&quot;,<br>      &quot;terminal&quot;: &quot;2&quot;,<br>      &quot;gate&quot;: &quot;9&quot;,<br>      &quot;aircraft&quot;: &quot;B772&quot;<br>    }<br>  }</pre><h3><strong>Kafka：流資料的資料庫</strong></h3><p>用最最簡單的方式理解，Kafka 就是能把資料用 event streaming 方式存起來的資料庫（當然他也不只資料庫的功能），他能接受資料用 streaming 的方式寫入，也能讓下游用 streaming 的方式依序把資料讀取出來。當然 Kafka 本身的眉眉角角就可以寫很多篇，但在這個專案裡是這樣子的：</p><p>Producer 把行李事件全丟進 Kafka 存起來，Flink 再從 Kafka 把資料讀出來、計算狀態後寫回 Kafka 的不同 topics（不同資料表），最後 ClickHouse 也支援從 Kafka 監聽資料寫進資料表。</p><p>為什麼不是直接寫資料庫？因為串流系統最重要的是 <strong>可重播、可分流、可解耦</strong>。Kafka 就像「時間序列的事實紀錄」，任何後端掛掉都能重新讀回來重算。</p><p>另外加上了一個 Kafka UI，方便本地 debug 以及即時觀看資料。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RoER7F65gjtWfZnOu4Oo6Q.png" /></figure><p><strong>Topic 列表</strong></p><p><strong><em>📌 事件與狀態類</em></strong></p><p>- baggage.events.v1：原始事件流（Retention），所有 bag event 都從這裡開始。</p><p>- baggage.bag.latest.v1：每顆 bag 最新狀態（Compacted）。BagLatestJob 會寫進來。</p><p><strong><em>📌 KPI 與警報</em></strong></p><p>- baggage.flight.kpis.v1：FlightKpiAlertJob 輸出的航班 KPI。</p><p>- baggage.alerts.v1：若 missing / freshness 超過門檻會在這裡產生警報。</p><p><strong><em>📌 行程狀態</em></strong></p><p>- baggage.notifications.v1：ItineraryStatusJob 產生的旅程狀態通知（AT_RISK / DELAYED / EXCEPTION）。</p><p><strong><em>📌 基礎資料</em></strong></p><p>- flight.schedule.v1：航班排程資料（Compacted）。</p><p>- baggage.assignment.v1：行李分配資料（Compacted）。</p><h3><strong># Flink Jobs：這套系統的「腦袋」</strong></h3><p>流資料進入系統以後，我們需要把「事件」變成「狀態」與「指標」。</p><p>這裡有三支 job，分別對應三個不同層級的狀態：<strong>Bag / Itinerary / Flight</strong>。</p><p><strong>1) BagLatestJob：每顆行李的「最新狀態」</strong></p><p>因為真正想查的是：「這顆 bag 現在在哪？」</p><p>但原始事件流裡，每顆 bag 可能有 6～8 個事件，不可能每次查都掃全事件。</p><p>我們用 SQL window 直接取每顆 bag 最新事件：</p><pre>SELECT bag_id, itinerary_id, customer_id, flight_id, leg_index, airport,<br>event_type, event_time_ts AS event_time, ingest_time_ts AS ingest_time, attributes<br>FROM (<br>SELECT e.*,<br>ROW_NUMBER() OVER(<br>PARTITION BY bag_id<br>ORDER BY event_time_ts DESC, ingest_time_ts DESC<br>) AS rownum<br>FROM baggage_events_view e<br>)<br>WHERE rownum = 1</pre><p><strong><em>輸出</em></strong></p><p>- Kafka baggage.bag.latest.v1（compacted）</p><p>- ClickHouse baggage.bag_latest</p><p><strong><em>設計 trade‑off</em></strong></p><p>- ✅ 查詢非常快</p><p>- ✅ 每包只留最新狀態</p><p>- ⚠ 如果需要完整歷史，仍然要查 bag_events</p><p><strong>2) ItineraryStatusJob：旅程層級狀態</strong></p><p>單顆 bag 的狀態不代表旅程是否正常。</p><p>旅客可能有多件行李，多段飛行，真正需要的是「旅程狀態」。</p><p><strong><em>依 itinerary_id 取最新事件 → 映射 journey_state</em></strong></p><pre>SELECT<br>itinerary_id,<br>CASE<br>WHEN event_type = &#39;TransferIn&#39; THEN &#39;AT_RISK_FOR_CONNECTION&#39;<br>WHEN event_type = &#39;ReassociatedToNextLeg&#39; THEN &#39;RECOVERING&#39;<br>WHEN event_type = &#39;LoadedOnAircraft&#39; THEN &#39;ON_TIME&#39;<br>WHEN event_type = &#39;Offloaded&#39; THEN &#39;DELAYED_OR_REROUTED&#39;<br>ELSE &#39;EXCEPTION&#39;<br>END AS journey_state,<br>event_type AS last_event_type,<br>airport AS last_airport,<br>event_time_ts AS last_event_time,<br>ingest_time_ts AS updated_at<br>FROM ranked_events<br>WHERE rn = 1</pre><p><strong><em>輸出</em></strong></p><p>- Kafka baggage.notifications.v1</p><p>- ClickHouse itinerary_status</p><p><strong><em>設計 trade‑off</em></strong></p><p>- ✅ 每個旅程只有一個狀態</p><p>- ⚠ 規則是簡化版（未涵蓋所有航空真實狀況）</p><p><strong>3) FlightKpiAlertJob：航班 KPI 與警報</strong></p><p>營運層級不是看單一 bag，而是看航班。</p><p>像是「裝載率」或「缺件率」才是營運 KPI。</p><p><strong><em>10 秒視窗聚合</em></strong></p><pre>SELECT<br>flight_id,<br>window_start,<br>window_end,<br>COUNT(*) AS events,<br>SUM(CASE WHEN event_type = &#39;LoadedOnAircraft&#39; THEN 1 ELSE 0 END) AS loaded,<br>SUM(CASE WHEN event_type = &#39;LoadedOnAircraft&#39; THEN 0 ELSE 1 END) AS missing,<br>TIMESTAMPDIFF(SECOND, MAX(event_time_ts), CURRENT_TIMESTAMP) AS freshness_sec<br>FROM TABLE(<br>TUMBLE(TABLE events_with_proc_time, DESCRIPTOR(proc_time), INTERVAL &#39;10&#39; SECOND)<br>)<br>GROUP BY flight_id, window_start, window_end<br>### Severity 判斷<br>CASE<br>WHEN missing &gt; 0 AND freshness_sec &gt; 120 THEN &#39;CRITICAL&#39;<br>WHEN missing &gt; 0 THEN &#39;WARN&#39;<br>ELSE &#39;OK&#39;<br>END</pre><p><strong><em>輸出</em></strong></p><p>- Kafka baggage.flight.kpis.v1</p><p>- Kafka baggage.alerts.v1</p><p>- ClickHouse flight_kpis</p><p><strong><em>設計 trade‑off</em></strong></p><p>- ✅ KPI 實時、好視覺化</p><p>- ✅ 方便告警</p><p>- ⚠ 10 秒 window 是 demo 用，真實可拉長</p><h3><strong>ClickHouse：可視化的資料中樞</strong></h3><p>如果說 Flink 及 Kafka 是必帶，那 ClickHouse 就是一個有比較多競品/選擇的 component。</p><p>做可視化這塊，我的想法是 Grafana 雖然有很多優點（高度客製化、支援 adhoc query以及多種資料來源、可以串 alerts），但其實他就是忠實呈現他底下的 data source，如果資料來源不快，那 dashboard 就不可能快。所以我更重視選擇可視化底下的資料庫這部分。</p><p>ClickHouse 是分散式的 column-based OLAP 資料庫，翻譯出來大概就是：分散式代表可以支援巨量資料及水平擴展、column-based 代表速度快延遲低的查詢速度（也得益於大量的 in-memory cache）、OLAP （對比 OLTP）代表對於大規模查詢特化而非單筆數據的 CRUD。我選擇他的原因，一部分當然是因為這是業界很受歡迎的選擇，另一部分就是看上他的快，由於我的目標是實時或是近實時的行李狀態追蹤。</p><p>關於 Kafka 資料寫入到 ClickHouse 的部分，Clickhouse 本身就支援用 Kafka 當成資料來源，並且可以設置 Materialized View 來省去一條 ETL pipeline 直接把資料寫入表裡。</p><p><strong>總共有四張表</strong></p><p>- bag_events：原始事件</p><p>- bag_latest：最新狀態</p><p>- itinerary_status：旅程狀態</p><p>- flight_kpis：航班 KPI</p><h3><strong>Grafana Dashboards：資料可視化</strong></h3><p>- Flight Bag Status</p><p>- Passenger Bag Status</p><p>- Fleet Coverage</p><p>- Bag Detail</p><p>- Itinerary Exceptions</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qwnYTWsK_xdDwcbk1zoskg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A0WYeeeCcZslxmAngZXqFw.png" /></figure><h3><strong>Kubernetes：集群管理</strong></h3><p><strong>為什麼把整套系統跑在 K8s？</strong></p><p>有一個沒有列在上面架構設計裡，但是我特別想提到的是，整個專案是 containerized 跟使用 k8s 的。當然如果只是要跑在本地上、跑在我的筆電上開發測試，k8s 是完全不需要的。我想展現的是真的 production 環境會怎麼架設，這也是一個我覺得自學的時候會有的 gap，看了一堆 k8s 或是 flink 的教學，但是沒有辦法想像全部兜在一起會長怎樣。</p><p>K8s 的優點是：</p><p>- 每個元件都是獨立部署，方便拆解與理解</p><p>- 有 restart / rollout 的能力</p><p>- 比較接近生產環境思維（尤其是 Flink / Kafka / ClickHouse）</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fViRWPt6cMJ8LLlOL9rAUw.png" /></figure><p>舉一些更實際的例子，比如說</p><ol><li>k8s 是所謂的雲原生（cloud native），他可以很方便的跟公有雲平台整合（Azure, AWS, GCP），代表我們這個專案已經可以直接部署上雲端，且享受公有雲平台提供的方便性、安全性、擴展性。如果今天資料量變大了，可以透過更改 k8s 設定來為 Kafka（存儲）、Flink（計算）增加更多節點，且因為 Kafka 及 Flink 都是分散式的平台，增加節點（機器後）就可以馬上有接近線性的效能提升。</li><li>所有配置/設定都是 Infrastructure as Code (IasC)，包含 producer 的參數、ClickHouse 的建表、Grafana Dashboard 的定義，都是百分之百寫進程式碼的，也就是任何 clone repo 的人（當然包含線上部署環境）都可以做出跟我本地看到一樣的東西，不需要去手動設定參數。</li><li>k8s 把每個元件獨立開來，意味著：只要輸入跟輸出的 Kafka topics 不變，我們可以很簡單的把 Flink 換成 Spark streaming。哪天覺得 ClickHouse 不好用，也可以隨時換資料庫。每個 pod 各司其職，有清晰的分界。</li></ol><h3>結語</h3><p>就像一開始提到的，選定這個專案有一部分原因是：網路上很多 Flink, Kafka, K8s, ClickHouse, Grafana 的教學，講得很仔細很完整，但是卻很少文章在闡述整個完整的系統。這個專案的重點不是做出一個漂亮的 Dashboard，而是從事件模型、狀態機、KPI 到 OLAP 查詢完整走一遍。只要理解這套結構，你幾乎可以把它套用到 IoT、金融交易、物流追蹤、甚至推薦系統。</p><p>當然，side project 距離真的 production 還有很大的一段距離，記得以前在做 streaming data 時，光是管理 kafka 集群（在相同的一群機器上榨出最大效能）或是搞定 stream processing 邏輯（狀態管理、streaming semantic、災害復原），每個都是單獨可以做上幾個月的大專案。但還是希望能給 streaming 方面的新手們一個方向，一起體驗這個有點迷人的領域（當初讓我一頭栽進的就是對比 Hadoop, Spark 等 batch processing 來說難以想像的秒級或甚至 sub-second 級資料處理速度）。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b06874438f1b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[FAAMG內部轉組指南]]></title>
            <link>https://medium.com/@tobby168/faamg%E5%85%A7%E9%83%A8%E8%BD%89%E7%B5%84%E6%8C%87%E5%8D%97-eb600b389566?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/eb600b389566</guid>
            <category><![CDATA[careers]]></category>
            <category><![CDATA[career-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[work-life-balance]]></category>
            <category><![CDATA[faang]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Wed, 28 Aug 2024 09:16:26 GMT</pubDate>
            <atom:updated>2024-08-28T09:16:26.315Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*edD8Xa027cf9gH1HSo7Tjw.png" /><figcaption>圖來自網路</figcaption></figure><p>由於自身的內部轉組經驗相對豐富（包含主動以及被動轉組），身邊同事朋友們經常會找我諮詢內部轉組的各種眉眉角角，諮詢內容不外乎是轉組流程或是怎麼挑選適合的組別、職缺。基於DRY（Don’t Repeat Yourself）原則，我覺得是個好時間點來統整這些內容並分享出來。</p><p>Disclaimer：本文純屬個人心得分享，不涉及任何公司內部資訊。</p><h3>想轉組的理由</h3><p>我認為最先要釐清的就是想轉組的理由。</p><p>有些人嚷著要轉組說了半年一年還是沒動，有些人因為經理的一句話就決心要走。</p><p>轉組的理由不外乎就是兩個種類：忍不了了、或是想追求更好的。</p><p>忍不了了的理由千千百百種，可能是受到不公的對待、遇到雷同事、工作內容無聊，總而言之就是嘗試（或是沒嘗試）改變現狀無果後，想透過換組來跳脫苦痛的循環。如果是這類型的轉組理由，我覺得最先要想清楚的是你的 deal breaker 是什麼，畢竟每個組都有他好以及不好的地方，可能你終於擺脫了雷同事，結果遇到了不喜歡的工作文化，所以弄清楚自己能接受什麼不能接受什麼挺重要的。當然，我沒有要很雞湯地說換組沒辦法解決根本問題之類的鬼話，我同意現實職場中確實有很多問題沒辦法靠一己之力改善，這時候逃跑真的是一個可行且能有成效的選項。</p><p>對於想追求更好而轉組的動機，因為內部轉組通常薪資待遇不會改變，所以不外乎是以下幾類：</p><p><strong>1 — Career 成長</strong></p><p>通常在升職後，對於職涯有追求的人會開始思考自己的下一步是什麼，可能在已經 build trust 的團隊裡繼續追逐下一個 level，也可能現組已經沒有足夠的機會以及Scope 到下一個 level。</p><p>這邊我想提出的是我認為 Career 成長的機會與個人能力成長的機會雖然正相關但並不完全掛鉤。「理論上」個人能力成長到 capable of next level’s work，就能升職，雖然實際上也是，但時間上可能不是最 optimized 的。更常看到的例子是還沒準備好的人碰到一個好的機會就上去了，或是苦等升不上去的人換了組後不久馬上升職。很多個人能力外的外在因素。因此換組尋求更大的 Career 機會是個挺常見的動機。</p><p><strong>2 — 學習機會</strong></p><p>有時候想換組並不是組不好，而是待太久了，能學的學得差不多了。學習機會的邊際效益遞減非常明顯，通常剛到一個新組總是覺得眼花撩亂、學也學不完。然而如果不是特別偏門或底層的組，我想通常待個兩年後就能駕輕就熟，尤其一般業務組無非就是在組習慣的技術棧之上去堆新功能，做久了總是會膩，也會怕待久了動不了。</p><p>當然學習機會不僅限於技術上，越往上走也需要軟實力上的學習機會。如果在組裡一直是小弟，可能就沒辦法鍛鍊自己帶專案還有帶人的能力，這時候跳到一個剛成立的組可能就能大幅度地訓練這些面向。</p><p><strong>3 — WLB</strong></p><p>在FAAMG，不同部門的生活可以是天與地，同樣是領差不多範圍薪水的 L4，有的人每個月 oncall 一次每天半夜被 page，有的人每半年輪一次甚至不用 oncall。</p><p>有些人因為較有企圖心而選擇了忙碌的生活，但是有更多人是在一無所知的狀況下被分配到一個忙組。如果工作量是不 sustainable 的，且短時間內看不到改善的希望或終點，這時候主動尋求機會跳脫也是無可厚非。帶入有些人說工作就是把自己的時間賣給公司的比喻，當然得幫自己找個好 deal。</p><p><strong>4 — 轉換跑道</strong></p><p>在職涯初期，有些人為了上岸優先而誤打誤撞進入了一個自己不喜歡的領域，或者就是正在探索自身興趣的過程中，於是希望在某個時間點轉換到一個全新的領域。這種類型的轉組我覺得比較 tricky，難免會擔心自己的經驗能不能夠 transfer 以及 hiring manager 會不會接受這個領域的小白。</p><p><strong>總而言之</strong>，我覺得先想清楚轉組的動機以及對於新組的期望是很重要的第一步，之後才能開始談怎麼找組。接下來我想用大部分的篇幅分享我在找組時主要會從哪些面向思考觀察，有點類似 reverse engineering，希望在一個組的外部，透過搜集各種 signals 來拼湊出這個組的全貌。</p><h3>升遷機會</h3><p>雖然每間科技公司的升職方法論差異甚大（F與G的標準就差別很大而導致常常引發討論），但還是有些共同點值得思考與探討。</p><p>除去個人能力外，我覺得有三個不可控的外在因素，是在加入一個組前可以有機會主動去篩選的，他們分別是：Budget、Scope、組織架構。</p><p><strong>1 — Budget</strong></p><p>升職被打槍的 No.1 理由：「Budget不夠」。雖然半真半假，但套句我前經理的說法：「如果有無限的 Budget，那我還不把我手下全部升成 Staff，我直接就變 Director 了？」。很多時候經理是想幫手下升職的，但 Budget 有限，經理們對內需要挑選想升哪些人，對外要跟其他同事爭奪名額與預算。</p><p>這時候很自然地會想，有沒有比較富裕的 org 呢？</p><p>我覺得答案顯然是有的。</p><p>從一畝三分地或 Blind 上有多少文章討論哪個 org 升得快哪個 org 升得慢就知道了。尤其早年 Google 的 Ads core 或是稍微近期一點 Meta 的 ig reels 組各種火箭升職的個例，就知道 budget 在組間的差距是可以很明顯的。</p><p>另一個想法，一個已經運行十年的成熟產品、各組都充斥著 Staff Engineer，與一個剛開始的新產品、今年剛招進 200 個 new hires，明顯後者更有上升空間。在職場上所謂的 Ladder 無非就是個金字塔結構，每 100 個 IC 就會需要 10 個 manager 帶、 10 個 manager 可能又會對到二三四個 skip managers、然後最後對到一個 director 之類的，外找的人通常招募速度慢且不一定能 survive（還貴），所以通常公司通常都偏向先提拔內部的人選。換句話說，招人多的地方就有機會！</p><p>那在轉組的時候要怎麼識別這個機會呢？除了該爬的文要爬（進到惡名昭彰的組真的不能怪誰…），我個人是會在面試的時候、當 hiring manager 問你有沒有想問的問題，發問：「你這個 headcount 是怎麼來的？你預計招幾個人？都是什麼 level？」。有些 HM 會非常地 defensive，覺得你在問他組隱私或是干你屁事，遇到這種我自己高機率會選擇放生，稍微有點 sense 的經理通常會知道你問這些問題的背後含義，並且這會成為你的加分項（遇到一個對職涯有追求的下屬總比遇到不積極的下屬好）。</p><p>Headcount的來源無非就是幾個：新組/新專案、現有專案成長、常規性招人、Backfill（有人走了所以補人）。從前到後依序（通常）代表了富裕程度以及不穩定程度，越有錢且越不穩定的地方，越容易讓新來的人有機會上位。</p><p><strong>2 — Scope</strong></p><p>Scope 是個說起來很玄的東西。</p><p>經理們通常不會告訴下屬，但在升職討論中，一個時常被討論的議題就是 scope。不管是候選人有沒有做到下個 level 的 scope、或是候選人升職後組裡有沒有能力提供足夠的 scope，總之都是考驗經理的挖坑能力（不是挖坑給人跳的那種挖坑，是擴展業務內容的那種挖坑，俗話說：「經理負責挖坑、下屬負責填坑。」）。</p><p>一個很現實的考量，公司的存在就是為了賺錢，如果今天這個功能我們能請一個 Senior Eng 完成，為什麼要把這個 Senior 升到 Staff 後用 Staff 的薪資支出來完成呢？</p><p>當一個人到達下一個 level 的能力後，即使組裡有錢，經理也要有足夠的理由說服他的經理以及同事，我們需要把這個人升到下一個 level。當然今天如果是 SWE1-&gt;SWE2 或是 SWE2-&gt;Sr.SWE，那大概都是沒問題的，但越往上爬越吃這套，比如很多公司是有鎖死每個 org 的 VP 總人數的（最簡單的例子，你經理的同事問你經理：「你們組已經那麼多 Senior 了，這次名額該給我們了吧？」，你經理應該要怎麼回答？）。這時候換組的時候就需要思考哪個組哪個 org 甚至是哪個產品有更大的上升空間，至少可以在下次升職時不必擔心組裡 scope 不夠的問題，減少一個煩惱。</p><p>我個人對於 scope 的衡量標準非常地主觀以及武斷：我認為跳脫自己的舒適圈就略等於一個高於自己 level 的 scope。如果你今天覺得這 JD 開得像開玩笑，一個 SWE2 你對他要求這麼高，那估計之後升職會議上就不會有 Scope 問題。如果你今天覺得這組的野心明顯跟人數不成正比，加入後會被操爛，那估計之後升職會議上也不會有 Scope 問題。</p><p>簡單來說，機會跟辛苦程度還是很大程度正相關的。還有一部分跟經理的挖坑能力以及畫餅能力（吹噓能力）相關，這個本文後面再討論。</p><p><strong>3 — 組織架構</strong></p><p>如果你在公司當過面試官，你就會知道，要絕對地公正是一件超級超級難的事。理想上，你心中有一把無比清晰的尺，你知道過了什麼標準的人我們要 hire，少一分都不行，但現實中，每個候選人的面向都是無限維度的，而且你很容易被前一個後一個候選人、以及同事的水平所影響，你心中的那把尺飄忽不定，你只能盡可能的把它穩定在一個可接受的範圍內。第一個候選人你不太確定，但與第二個第三個對比後，你就能比較肯定了。</p><p>一樣的道理在升職決定上，今天把一個人丟給你的經理，問他這個人準備好升職了嗎，他會給你一個答案，但他沒辦法很肯定。實務上，最常見的做法就是，拿升職候選人與當前等級以及下一個等級的人做比較，只要你的能力/輸出勝過多數同等級的人並且不輸半數下一個等級的人，那我們就能肯定升你上去不會犯大錯（很諷刺的是，對於很多人來說此種大事件，經理們追求的只是不要犯錯。畢竟升錯一個人，之後要再升人就難了）。</p><p>說到這就能發現，經理們下判斷時，竟然有這麼大程度是取決於你跟誰比、並非你的能力絕對值。當然多數時候你不能選擇自己的同事 — 但轉組的時候是例外，當你轉組時，你可以觀察下你的下一個等級的同事多不多（判斷有多少位子留給你以及潛在的一起比較對象），以及 Linkedin 上查查各同事在當前等級待了多長時間（推測這組的升遷速度、同事的企圖心、以及按資排輩的話你排第幾個升職等）。簡單來說，金字塔型還是比較健康的結構所以盡量避免倒金字塔，以及稍微觀察同事屬性來判斷升職可能性。</p><h3>學習機會</h3><p>在考量未來組的學習機會多寡時，除了最基本的，這個組做的工作內容是不是有興趣以及想學的。我覺得有八九成都取決於你的未來同事們。</p><p>如果加入新組後你的 level 比你其他同事高，那別人可能會期望你擔起 mentor 或是 leader 這種更偏向輸出的角色，你可能能夠磨練自己的軟實力，但是技術上的成長就會比較有限。反之，如果組裡很多比你資深、或是在這個領域深耕多年的人，那就可以預期在被當小弟使喚的同時也能接收很多來自同事的指導。我覺得在軟體開發這方面，有個比你資深的人指導滿重要的，很多時候雖然能靠自己想出一個可行的解法，但是有些演算法、技術、設計模式，如果沒有人跟你提過這個關鍵字，你就得走過超級多彎路才能找到一點門道。</p><p>再次強調「面試是種雙向選擇」。基本上內部轉組的面試官都是未來的同事或是同個 org 裡的人，所以面試時也是個機會讓你實際跟未來同事溝通合作，了解未來同事的能力以及專精領域。</p><p>我個人還滿喜歡在公司裡找「假想 role model」，因為我覺得公司裡很多在軟體業打滾十幾二十年的前輩，能夠看到形形色色的人，能夠幫助我自己想像一兩年五年十年後我想變成怎麼樣的人。同樣的道理套用在判斷新組的學習機會上，看看這些未來同事（面試官），去思考如果待在這個組一兩年後能夠學到這些人會的這些東西，是不是一個我想追求的狀態。</p><p>另外，我覺得一個基本的原則是，想要學習就要有付出時間的心理準備。大部分的人都是在做著跳脫舒適圈的事時成長最多。雖然說要學習就要付出時間，但也不是說忙就會進步，有的組雖然忙卻在忙比較沒有意義的事，這時候還是要善用各種內部工具來慢慢識別，翻出所有你能看到的 design doc、code review、presentation。</p><h3>Work Life Balance</h3><p>如果今天換組的目標是改進 WLB，我建議用排除法，以下是我覺得比較明顯的 red flags：</p><ol><li>Reputation：如果這個產品、org、組在網路上的名聲很臭，那真的要信，我們絕對不會是幸運的那個 outlier。該做的功課真的要做。</li><li>Tickets 數量：很多公司可以查各組的 oncall tickets 數量，這種幾乎是內部公開資訊的東西當然也是一定要看的。</li><li>同事上線時間：申請職缺前或是接 offer 前，花個兩天白天晚上都看看未來同事的上線狀態就能知道以後大概的工作體驗了。</li><li>Job Description：如果在 JD 裡看到 responsible for oncall duty 之類的字眼，我百分之百不會投… 大家都預設工程師工作內容需要 oncall 了，額外寫出來太可疑。</li><li>Diversity：不太有科學根據，但網路上流傳 Diversity 不高的組（全是華人之類）通常都很忙。我個人經驗也是這樣。</li><li>流動率：可以大概 Linkedin 上查查新組同事都在這組待了多久，待很短可能不是組的問題，但如果有人待很長，那應該 WLB 不會太差。</li><li>組織架構：如果組有很多在其他時區的同事或合作組，那通常工時都會很長。</li></ol><p>不過我也要再次強調，在追尋 WLB 的時候要把自己的心態擺正，閒組的 trade-offs 就是升職較慢以及有更大的裁員可能性（或是可能上班時間會太無聊這個奢侈的煩惱）。</p><h3>跟對老闆最重要</h3><p>我個人一直都相信，直屬經理的好壞影響了工作體驗的 80%。一個好的經理可以給你適當的工作量、能夠在他可控範圍內給你適當的彈性、能在你專案延遲的時候理解背後的理由、能在需要吵架的時候在你面前替你擋刀、能在替你爭取利益的時候不嫌麻煩。</p><p>因此在選組的時候，我個人是 prioritize 經理優先於組的工作內容或是其他因素的。</p><p>我自己會把經理們放在三個維度上比較，分別是：聰明程度、上進心、肯為下屬付出。</p><p><strong>1 — 聰明程度</strong></p><p>我個人認為選擇聰明的經理以及平庸的經理各有好壞（想起來也很好笑，什麼時候輪到我評論自己的經理了）。</p><p>聰明的經理（或同事）合作起來總是讓人覺得很舒適，討論事情能夠一點就通。但相對地很多聰明的經理可能會有比較固執或是愛自幹的問題，或者是對於下屬的要求較高等等。</p><p>不是那麼聰明（就平均值吧）的經理也沒有什麼明顯的壞處。如果是一個 high level IC 我可能會期望他比較聰明或是很有工程上的經驗，但對於經理我甚至覺得同情心的比重應該要是大於聰明程度的。畢竟經理的工作就是在這樣一間大公司裡協調組內外事務，我管他是用聰明方法還是笨方法，能夠站在各種角度思考並提出可行的解法我認為就是合格的經理了。</p><p>因此，以聰明程度來說，我覺得更像是個人偏好，有的人更喜歡跟聰明的人合作並且承受高壓一點的環境，有的人更偏向在其他的維度上去做取捨。</p><p>在考察經理的聰明程度方面，我覺得在面試的過程中能夠滿明顯的去觀察（我個人真的滿信奉面試是雙向選擇的這個說法的）。從經理出的題目難度以及臨場反應，我相信不管是誰都能略知一二（但要記得面試的時候不要光顧著緊張、也要記得觀察面試官）。</p><p><strong>2 — 上進心</strong></p><p>前面有提到，經理的一大工作內容是「挖坑」。我認為經理挖坑的能力以及他挖坑的意願是互相獨立的。而這邊我想說挖坑的意願取決於經理的上進心（想挖卻挖不動就是能力不足了）。</p><p>進入職場後，難免能體會到，每個同事都是處在不同的人生階段：有些人剛畢業滿腔熱血、有些人不知道受到多少傷害已經躺平、有些人重心在家庭不在工作、有些人家逢變故自顧不暇、有些人在升職邊緣想奮力拼搏一波。這件事在經理們身上尤為明顯，他們通常都是十年工作經驗以上，鬼才知道他們現在正在想什麼呢。</p><p>直屬經理的上進心能夠影響很多事，包含了 WLB 以及前面提到的組的 Scope。如果我是個有上進心的人，那我多半也會選擇一個有上進心的經理。如果我今天想躺平，那我最好不要碰到一個正處在事業上升期的直屬經理。</p><p>怎麼判斷經理的上進心呢？這是個大哉問。</p><p>我自己會從兩個方向去做推敲。</p><p>一個是經理開出的 Job Description (a.k.a. JD)。如果今天你打開一個科技公司的招募網站，你多半能很明顯地區分出兩種 JD：一種是超級 general 你不知道在供三小的、一種是明顯列出想找什麼樣的人的。這應該不用我多說了吧，一個非常 general 的 JD 給我的感覺就是經理在打混，不管是懶得花時間想文案（有些看起來就是複製大組裡的通用模版）還是不清楚自己需要什麼樣的人才，不可能有人忙到連一頁 A4 紙的內容都沒時間想，何況這是你未來要一起共事數年的同事啊。</p><p>另一個是經理心中有沒有未來的藍圖。我自己在面試時很愛問 hiring manager 有關未來的那種虛無飄渺的問題，我知道他能唬我，但我也遇過無數當場愣住三到五秒的經理。我會問比如說「你覺得這個組的機會在哪裡？」、「你心裡現在有什麼想做但騰不出人力做的專案嗎？」。我覺得一個有上進心的經理不只要會給下屬畫大餅，也要會給自己畫大餅。哪裡有機會能開拓、哪些東西需要去嘗試（然後 90% 會失敗），要往上看才有可能真的往上爬。而這些問題也是一個有上進心的經理應該常常在思考的，所以在面試中被問到應該也要能信手捻來才對。</p><p><strong>3 — 肯為下屬付出</strong></p><p>這點是我覺得最重要的。跟到一個肯為下屬付出的經理完全就是上輩子燒了好香。</p><p>肯為下屬付出的程度與前兩項 — 聰明程度與上進心 — 是互相獨立的。</p><p>在很多討論怎麼當好一個經理的文章中，很多人提到，一個好的經理的先決條件，是你能夠為你下屬的成就而感到開心。就是所謂的付出型人格吧我想，不是每個人都適合當經理的。</p><p>舉個例子，某一年在我極度操勞後，我問我經理：「我知道你打算下個季度幫我升級，但是我覺得我的付出遠大於一般人對於我這個等級的期待，你能不能試試這個季度幫我提交申請呢？」，我經理回我：「你在這個等級的年限還不夠長，這個季度申請的話屬於特例，需要很多資料佐證。你就不能再等三個月到下一季嗎？」。當下我的心真的涼了半截，過去半年我為你賣命是為了啥，你就提交一個申請、該交的資料交一交，到底是有多難？</p><p>再舉個例子，有個朋友長期處在超級高壓的工作環境中（半夜睡一睡會被手機振動嚇醒以為被 paged 的那種高壓），平常他的經理各種不合理的要求他都義不容辭。有次，他家出了點狀況需要回台灣一趟，他申請了兩週的假，他的經理竟然說突然休兩週太影響進度了，還是希望他回台灣時間能夠遠端工作。當時，這位朋友就覺得發覺，這個經理從頭到尾在意的只有工作產出，並沒有把他當成一個人來對待。</p><p>我覺得找到一個肯為下屬付出的經理，能夠更放心地工作。你可以在認真工作的時候不用擔心拿不到該拿的 credit、遇到任何突發狀況（工作上和工作外）的不安全感也會大幅下降、也不用整天揣測老闆說的是真是假。</p><p>不過實話是，在真正加入組和經理共事前很難了解到他會不會是個肯為下屬付出的人。我覺得在面試中詢問經理 behavior questions 的效果不太好，能當到經理的人通常都很能說（而且職場經驗也比我們多）。通常我會在通過面試後到取得正式 offer 之間的幾天做一個迷你測試，比如說請經理幫忙爭取數字更好的 offer、或是跟經理協調換組日期、或是溝通未來的休假計劃等等。也就是要想辦法麻煩經理去做一件事，這件事稍微有點麻煩（不是一兩封信就能解決的），然後看看經理的反應，是會找藉口推托還是會接下任務，以及後續執行行動時的積極程度。</p><h3>其他想聊的</h3><p><strong>1 — 有的職位不是開給你的</strong></p><p>有時候，雖然職缺開的是 Senior Engineer，而你也是 Senior Engineer，但你卻不是他們想找的對象。</p><p>如果你參與過招募流程你可能就會發現，常常 hiring manager 心裡已經有個想法他想招募什麼樣的人，但是這個條件很難明確地反映在 JD 上。比如說，組裡想招個經驗豐富的 architecture，但是經理只爭取到了 Senior 的 headcount，這時候他可能會想辦法盡可能地在應徵者裡找到最有經驗的那個，而剛升上 Senior 的你就在無形中被排除了。</p><p>這種事在外部是很難知道的，我覺得首先要做的是降低自己的得失心，申請了職位後就忘了它，拿到面試那就是多賺的。再來少數能做的事就是從 JD 中去嗅出線索，站在 hiring manager 的角度去思考開出這樣 JD 的話是想找怎樣的人，一個盲人摸象的概念。</p><p><strong>2 — 換跑道的適應性</strong></p><p>如果今天換組是為了轉換跑道（經常聽到的可能是前端換後端XD 或是 new hire 時被隨機分組，過了若干年後想追求自己有興趣的領域），最常見的擔心是過去的不相關經驗能不能被認可，以及轉組後能不能順利。</p><p>我個人肉身嘗試了XD 我覺得 Senior 以下如果不是很特別的領域（AR、Gaming、Desktop App…）應該都是隨意切換的。一個是本來 new hires 的時候也是隨機分配居多，所以很多組不預設新人有相關背景。另一個是 transferrable 的技能意外的多，包含公司的 telemetry 系統、工作流程、專案管理能力等，其實在內部轉組時已經不能視為過去經驗完全不相關了。而且如果想轉換跑道的話，內部轉比起外部投也會簡單很多，同樣的道理也發生在 Eng 轉 PM 或 IC 轉管理職。</p><p>3 — 善用你的優勢</p><p>在內部轉組時，同時也會默默地跟外部候選人競爭（反過來說，現在外部找工的地獄難度也是一部分來自比不過內部候選人）。不管是跟外部或內部候選人競爭同個職位，都需要最大化你的現有優勢。從一個很現實的角度來說，今天如果有很強的候選人，那我們優先選強的，如果有幾個差不多好的，那經理估計會選擇即戰力（包含入職時間以及上手時間），內部候選人在入職時間上已經有了優勢，而上手時間則一樣取決於你有多少可轉移的技能、經驗。</p><p>另一個內部候選人對於招募經理的好處是，他能夠有更多的資料佐證你的過去表現。因此，我自己在申請內部職缺時通常會先私下 reach out 招募經理，跟他說明我的各種優勢，我認為比存粹只按下招募按鈕好（至少善用了你能直接聯繫招募經理的優勢）。</p><p>我的 reach out 郵件模板大概長這樣：打招呼、簡短自我介紹（當前等級、兩三句話介紹自己的優勢跟經驗）、詳述自己的技能樹、想投這個職缺的理由、表明想 schedule 下一步 information call 或面試、感謝。</p><p><strong>4 — 面試時可以問的問題</strong></p><p>除了上面有提到一些能夠幫助了解經理的問題外，還有些問題不管是對招募經理或是未來同事都可以問的。</p><p>(a) 如果我加入這個組，我的 ramp up plan 是什麼，我有多少時間可以 ramp up？</p><p>這個問題可以問招募經理或是未來的 tech lead，ramp up plan 能稍微了解這個組有沒有一個比較標準的培訓新人流程，也能知道未來要帶領你的人腦袋裡有沒有一個你工作內容的藍圖。至少多少上手時間，則能知道這個組對於即戰力的需求有多急迫，以及忙碌程度XD</p><p>(b) 現在其他同事都在做什麼專案？</p><p>我自己滿常問這個問題的，因為我覺得比起可以畫餅畫很大的 JD，從其他正在進行中的專案更能認識這個組的真正工作內容。同時，也能側面觀察招募經理對於現有專案的描述能力及對細節的了解程度（如果經理能提到這專案最近遇到的困難點是什麼我就覺得很加分）。</p><p>(c) 現在都跟哪些組合作、什麼合作模式？</p><p>這個問題可以問問未來同事。跟哪些組合作可以再多一個線索這個組的實際工作內容，以及可以很大程度的了解 WLB。如果一個組在美國和印度都有人，那就是躲不過的頻繁早起開會、或者是如果一個組有太多的合作組那估計會有比較多的瑣事。</p><p><strong>5 — 轉組流程</strong></p><p>轉組流程我覺得各公司大同小異，無非就是：找職缺、聯絡招募經理、安排面試、口頭結果、正式 offer、討論換組時間。</p><p>需要注意的是在哪個時間點你的現經理會被通知。有的公司在你按下 apply 按鈕的那刻就通知了（甚至都還沒開始面試）、有的公司在接 offer 的時候才通知、有的公司根本沒有通知系統。</p><p>我覺得在換組的時候兩個大原則就是：沒有回頭路、minimize 現經理能反應的時間長度。很多軟體業鬼故事是在換組的中途被經理發現了，經理利用各種人資手段阻擋你換組，最後導致換組不成功，尷尬地留在本來的組。因此，去減少現經理能在背後做操作的可能性是很重要的。一般來說，在系統裡接 offer 後就比較穩一點了，相當於在人資系統上正式記下你的轉組，這時候經理要做什麼操作的難度都會高很多。所以我們要做的就是盡可能縮短「經理知道你在轉組」與「你在系統裡 accept offer」這兩個時間點中間的時間長度。</p><p>情理上（如果經理對你很好或你們一起合作了很久），我們會先通知經理、再接 offer，這代表我們對經理的尊重，不是先斬後奏，且留給經理挽留的空間。如果你的經理真的很賞識你（而且他是個希望用善意的手段留下你而不是惡意阻擋的話），在他得知你想走的意圖後他能夠去申請一些 retention offer（加薪或一次性獎金或升職等等），如果談不攏再放你走。這時候我就想提到「沒有回頭路」的大原則了，我認為不管是什麼複雜的情況或是吸引人的條件，如果已經讓經理知道了你想走，那就是一定要走了。我覺得不管經理多賞識你，經歷這一齣以後肯定還是會心有芥蒂，覺得你隨時會走，有些經理開出吸引人的條件也只是為了短時間內不讓組裡出現問題，把你留下後反手就開始計畫要怎麼降低你在組裡的重要性。當然如果今天你就是跟你的現經理不對盤，或甚至你想要噁心他一把，那直接在系統裡接 offer 讓系統去通知他你要走了也不是不行，甚至可以說是最保險的做法，但就是可能之後協調轉組時間會被找麻煩。</p><p>綜上所述，行業裡其實對於提出轉組是有套（近似於）標準流程的：我們可以跟經理線上約時間，用視訊告知的同時開著 accept offer 的系統，在通知他後隨時就可以準備按按鈕了（可以是通話結束後，或是視訊中看到經理表情不對或是頻頻動作打字就可以按了）。而約線上通話也不能提早約，如果約隔天的話可能經理當天晚上睡覺就突然想通了，最好是臨時問他說有急事能不能現在講個五分鐘（Do you have 5 mins for a quick call?），據說有經驗的經理也是看到這句就知道你要找他談什麼主題了。</p><p><strong>6 — 適當爭取</strong></p><p>通常朋友們收到我們公司 offer 後，我都強烈建議能 negotiate 就試試。常常你在加入公司前的一兩三封郵件往來，就有機會能爭取到不少的漲薪、甚至是更高的級別，雖然有時候有點不好意思去爭取，但爭取失敗了沒損失，成功了能換來以後的開心或省去一兩年的時光。新人在通過面試後加入公司前的 Leverage 是最大的，進入公司後就反過來了。</p><p>同樣的道理套用在內部轉組上，雖然效果可能沒有加入公司前好，但如果有什麼想爭取的我認為也是要在通過面試後接受 offer 前爭取，舉凡加薪、休假安排、工作分配，如果對加入組後有任何期許，都應該先提出來。沒有經理會因為你的要求而撤回已經發出去的 offer（代表你已經殺出重圍是他當下最好的選擇），相反的，他應該要欣賞你肯為自己爭取利益的態度才對，也算是對新經理的一個下馬威（下馬威這詞可能太強烈了， 比較像是讓他知道你是對自己對新組有要求的人，不能被隨意對待）。</p><p>我很同意以前在網路上看到的一句話：「HR 的規定沒有不能被打破的」，我覺得想要的都可以談看看，頂多就是被拒絕要求而已。像是很多人說內轉不能調薪，但事實是如果你的面試表現夠好，大部分公司都可以讓招募經理去幫你特別申請的，只是案例相對比較少而已。</p><p><strong>7 — 來到新組後</strong></p><p>到新組後，最最重要的就是建立好的第一印象。有好的第一印象以後什麼事都有事半功倍的效果。</p><p>我自己覺得到一個新組後一個小 tip 是可以尋找能夠發力的點，比如說舊組和新組的共通技術、或是甚至把舊組的技術引進到新組來，總之就是在自己還在學習新組的各種技術、人、工作模式的時候，早一點開始對新組有所貢獻，如果能找到你有個東西比新組的所有人都更擅長那更好，剛加入組時可以適當的更積極去爭取 ownership 以及 tasks。發力的同時也可以持續 ramp up，比較像是用你之前的經驗來擋一下，爭取更多的上手時間。</p><p>另外一個我覺得挺重要的是花時間了解新組裡每個人的所處狀態，比如有人正在拼升職、有人正在被 pip（咦）、誰喜歡做哪類型的工作等等。常常在加入新組後過了幾個月才回想起來，咦當初原來那個誰誰那樣做是因為什麼。我覺得人的部分也需要 ramp up，但常常被忽略。這方面我覺得剛開始可以比較積極的與大家聊天交流（我通常會直接全組約一輪 1:1），然後在各種場合多花些時間思考為什麼那個人要做這樣的事等等。</p><p>扯了一堆，這篇文主要的目的還是總結我目前位於內部轉組這件事的心得、想法、與觀察。希望能對網路上對於轉組還在思考或迷茫的人有所幫助。</p><p>另外在我上次發文<a href="https://medium.com/@tobby168/%E9%9D%A2%E8%A9%A6google%E5%9B%9B%E5%B9%B4%E7%B5%82%E6%96%BC%E6%8B%BF%E5%88%B0offer%E4%B9%8B%E6%84%9F%E6%83%B3%E7%B8%BD%E7%B5%90-9fa7f9717410https://medium.com/@tobby168/%E9%9D%A2%E8%A9%A6google%E5%9B%9B%E5%B9%B4%E7%B5%82%E6%96%BC%E6%8B%BF%E5%88%B0offer%E4%B9%8B%E6%84%9F%E6%83%B3%E7%B8%BD%E7%B5%90-9fa7f9717410">面試Google四年終於拿到offer之感想總結</a>後，在<a href="https://www.linkedin.com/in/tobbykuo/">我的 Linkedin</a> 上收到滿多陌生人的聯繫與詢問問題的，甚至還有幾個約了電話聊天。我覺得滿好的，很高興可以成為網路上資源的一部分提供有用的幫助。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eb600b389566" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[面試Google四年終於拿到offer之感想總結]]></title>
            <link>https://medium.com/@tobby168/%E9%9D%A2%E8%A9%A6google%E5%9B%9B%E5%B9%B4%E7%B5%82%E6%96%BC%E6%8B%BF%E5%88%B0offer%E4%B9%8B%E6%84%9F%E6%83%B3%E7%B8%BD%E7%B5%90-9fa7f9717410?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/9fa7f9717410</guid>
            <category><![CDATA[software-engineer]]></category>
            <category><![CDATA[google]]></category>
            <category><![CDATA[career-development]]></category>
            <category><![CDATA[interview]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Sun, 01 Jan 2023 02:01:41 GMT</pubDate>
            <atom:updated>2023-02-05T07:36:02.464Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QytrLfc5lHtazq2DM7itQg.png" /><figcaption>不知道寫這種文章需不需要人權圖（？）</figcaption></figure><p>在斷斷續續的努力了四年後，今年終於拿到 Google SWE L4 的 Offer，有種跨過一個里程碑的感覺。還記得多少個因為準備面試緊張睡不著覺的晚上，只能滑著手機看各種前輩們的面試心得（感覺 2019 年我是真的把所有網路上能找到的中英文 Google 面試資源都讀了一遍，每天花三小時翻面試心得），所以當時也決定要是有一天我也順利過了面試，也要把經驗分享出來，算是一種還願。</p><p>這篇文不會紀錄任何我遇到的題目（所以如果是來找題目的話可以停在這了），我會簡單紀錄下我四次面試經驗的過程跟心得，然後再來總結下這些年來準備面試的心得跟感想。</p><p>個人背景的話，比較值得提的應該是我是非本科系的學生（土木工程），在校期間全都是自己做 side projects，沒有在學校上過任何本科系開設的課程。目前在微軟當軟體工程師（偷引流一下我的另一篇文 <a href="https://medium.com/@tobby168/microsoft%E5%BE%AE%E8%BB%9F%E5%B7%A5%E7%A8%8B%E5%B8%AB%E4%B8%89%E5%B9%B4-%E9%9B%9C%E6%83%B3-cd3dedce827">Microsoft微軟工程師三年 — 雜想</a>）。</p><h3>面試經驗</h3><p><strong>2019</strong></p><p>應徵組&amp;Level：General Campus Hire（L3）, Google Taiwan</p><p>Leetcode 題數：~300（印象是 ~20 hard，剩下 easy、medium 各半）</p><p>準備時間：三個月，但因為還是學生所以幾乎一天八小時</p><p>面試過程：HR screen -&gt; Phone screen -&gt; Rejected</p><p>結果：Rejected</p><p>小結：</p><p>Phone screen 遇到兩題 medium 左右的題目，自己也沒有發揮好，但其中有一題不是常見的題型，一直跟面試官對不太上頻率。面試官讓我做一個數學證明，當下就感覺自己答的不是他想聽的，但是也不知道他想聽什麼XD 當年覺得自己運氣很不好（還有嘗試跟 recruiter 上訴但失敗），後來想想其實那時候準備的也不是很充分，要是到了 onsite 遇到 hard 題加時間壓力大概也是不行。</p><p><strong>2020</strong></p><p>應徵組&amp;Level：General Campus Hire（L3）, Google Taiwan</p><p>Leetcode 題數：~400（分佈應該跟 2019 差不多）</p><p>準備時間： 一個月，主要是感覺還沒開始就結束了。</p><p>面試過程：HR screen -&gt; Phone screen -&gt; No headcount</p><p>結果：Stopped after phone screen</p><p>小結：</p><p>這年有順利過 phone screen，不過後來疫情跟 hiring freeze 就開始了，recruiter 跟我聊的最後一通電話說是要去幫我問還有沒有組在招人，然後就沒有然後了。</p><p><strong>2021</strong></p><p>應徵組&amp;Level：Pixel（L4）, Google Taiwan</p><p>Leetcode 題數：~600（hard 刷到 50 題，剩下多的一百多題 medium 佔大部分）</p><p>準備時間：三個月，開始面試後才開始，面試了好久邊上班邊刷題好累。</p><p>面試過程：HR screen -&gt; Phone screen（L3） -&gt; Phone screen（L4）-&gt; Onsite（4 rounds coding + 1 round googleyness）-&gt; Team match (failed) -&gt; HC -&gt; Rejected</p><p>結果：Rejected</p><p>小結：</p><p>這年比較特別是一開始是 Target L3，想說自己也工作兩年了應該可以試試 L4，跟 recruiter 反映後多加了一輪 L4 難度的 Phone screen，之後就順利 Target L4 了。滿推薦大家這麼做的，Target L4 的話面不好還有被降成 L3 的可能，雙倍機會（？）</p><p>總體來說這年 onsite 遇到的題目不是很難，沒有遇到 hard，甚至有 easy 或是 leetcode 原題。不過因為題目不難所以感覺標準被拉得很高，自評這年很多題目的熟練度都不夠（解太慢或是有小 bug），最後拿到一個低空過關的評價。</p><p>Google Taiwan 有個流程是成績比較普通的話，可以先 Team match 再送 HC （Hiring Committee），如果有組比較想招你的話他們會幫你寫 Support Letter，可以增加一點過 HC 的機會。但我大概是成績太差了，努力找了一個多月只聊了一個組，後來也沒有拿到推薦。還是請 recruiter 幫我搜集資料送了 HC（就是想試一下，想說成本就是失敗的話冷凍一年也還好，感覺這年一直麻煩 recruiter 幫忙），大概一週就被拒絕了。</p><p><strong>2022</strong></p><p>應徵組&amp;Level：Google Cloud（L4）, Google Taiwan</p><p>Leetcode 題數：~650（題數沒有多，但今年改變刷題策略，後面分享）</p><p>準備時間：三個月，一樣邊上班邊刷題好累。不過中間有跨一個長假。</p><p>面試過程：HR screen -&gt; Phone screen -&gt; Phone screen -&gt; Onsite（3 rounds coding + 1 round googleyness） -&gt; HC -&gt; Team match -&gt; Offer</p><p>結果：Offer（finally!）</p><p>小結：</p><p>今年遇到很給力的 recruiter，從頭到尾給了非常多的幫助，不管是流程方面還是面試準備、還有最後協調薪資的部分。一開始是我的一年冷凍期還沒過，但不知道為啥被聯繫了並且說可以開始面試。開始面試的時候我也有點懶，問了能不能用去年的結果跳過 phone screen，結果 recruiter 很堅持說與其用去年低空的結果不如整個從來。結果還是因為第一場 phone screen 表現普通 recruiter 又幫我申請了第二場，不太確定但好像這樣就可以不看第一場的成績。</p><p>今年也剛好跨到 4/1 開始新的招募流程，所以 Onsite 少了一關變成三輪 coding，並且 L4 的第三輪可以選擇要考 System Design 還是繼續資結演算法（不過新流程好像更偏組招，所以不確定是不是大家都這樣）。本來想說 System Design 很新奇沒考過就想選，但 recruiter 也是強烈建議我繼續選 DSA，說 L4 不太考量系統設計的成績，這樣等於白白送掉一個表現好的機會。</p><p>和 2021 不同的是，這次每輪都遇到一題以上的 hard（是的，有傳說中的 45 分鐘兩題 hard），不過這年總體來說就是我狀況最好的一年，面試當下還以為他們都是 medium（汗）。最後成績好像也是不錯，順利送了 HC 並通過後就 Team match，申請跟三個組聊他們也都願意跟我聊（當下真的很感慨果然去年成績不夠好說什麼都無力回天），然後就是 Offer 了。滿不錯沒有被 down level，拿到 Offer 的當下工作年資兩年多所以也覺得很滿意。</p><h3>準備面試</h3><p><strong>刷題的幾個階段</strong></p><p>在刷題的過程中，確實有感受過幾個比較明顯的階段，雖然大家的經驗一定不相同，但是我也想分享我的。</p><p>一開始的階段是最痛苦的，其實我都差點忘了這階段了，但有時在聽一些 podcast 的 Q&amp;A 的時候這段回憶會被喚醒。第一階段應該是連怎麼「刷題」都不太會，跟 leetcode 的介面不太熟、就算上過學校資結演算法也沒碰過這種類型的題目、連第一題 two sum 都解不太出來（還記得第一次看 two sum 討論區的震撼）…。第一階段對我來說尤其痛苦，因為我沒有上過學校課程，所以一直不太知道自己的程度在哪，每題都解不出來對我來說打擊確實是非常大的，看著這些寫不出來的 easy 題根本沒法想像能寫出 medium 甚至是 hard 的一天。</p><p>這階段我事後覺得就是能做的事很有限，所以也不用想太多，有時間就多寫 easy 題多看討論區，抓到感覺後慢慢就能寫出 easy 了（我相信對大家來說是這樣子的！很多 easy 都是一個 for 迴圈能解決的）。另一個建議是，後來看我朋友刷題，覺得一件非常非常有幫助的事是找到一個有經驗的朋友或者找到一個同樣沒經驗的戰友，每週一起約個兩天寫題目，互相分享各自的發現跟學習，很快就能突破這階段了。</p><p>第二階段應該就是 easy 差不多了，開始慢慢碰 medium 的階段。我自己的感覺是，easy 題真的是設計來讓你習慣刷題這件事的（包括怎麼讀懂題目、處理輸入輸出、debug 測資等等），從 medium 開始才會加入各種演算法跟資料結構，比如說 graph、DFS/BFS、Dynamic Programming、Tree、Binary Search…。所以這個階段對我來說有兩個難處，一是考驗你過去對這些資結演算法的認識程度，二是考驗你應用這些資結演算法的能力。</p><p>Easy 跟 Medium 階段其實我都是鼓勵多看討論區的，很多時候多花兩小時沒辦法讓你把題目解出來，但這兩小時累積的壓力跟挫敗感非常巨大。重點是看討論區的時候不能囫圇吞棗，很多時候以為自己會的，隔一段時間回來還是解不出來，這種感覺也很難受。看討論區答案的話，我對自己的要求就是在不看討論區的情況下自己把那個解答寫出來。</p><p>如果在這階段發現自己對某個資結演算法不熟悉的話，我建議就是直接花兩天回去打掉重練，我很推薦 <a href="https://www.coursera.org/learn/algorithms-part1">Coursera 上面 Princeton University 的 Algorithm 課程</a>，也很推薦每個碰到的資結演算法都自己實作<strong>幾遍</strong>。這階段我感覺就是不能急，基礎不穩的話後面也很難進階的。</p><p>第三階段我覺得並不是直上 hard 題，而是把 easy + medium 這個範圍內練得盡量熟練。對於多數人來說我覺得 hard 題是完全沒必要的，除非明確要挑戰 Google，不然台灣大多數的公司包含 Microsoft, Amazon 都不是很常考 hard 題，並且，能在 40 分鐘內解出 hard 題的人是相對很少的，有些公司就算考了 hard 題也沒給出符合這種程度候選人的薪資水平，遇到就笑笑就好XD</p><p>能解出 medium 題跟能順暢解出 medium 題是有很大的區別的，也需要很大的努力才能成為後者。試想，如果面試難度訂在 medium，你沒辦法用解 hard 題來證明自己，那就只能靠熟練度了。面試官比起一個磕磕碰碰才解出題目的人，當然更偏好一個看到題就秒，不只解出題目還提出三種解法的人。所以我認為與其花時間跟 hard 題硬碰硬，不如投資時間去把自己的面試表現提升上來。</p><p>這階段比較常見的誤區我覺得是拼題數，當然講是很簡單，大家都知道不能一直無意義的堆題數，但真的很輕易地會被那種解題的爽感帶偏，一週寫了五十一百題但其實沒學到什麼。大家這階段應該都養成屬於自己的習慣了，所以我就不特別說應該怎麼做（我自己是經典題派，就是大多時候刷題號小於 500 的題目）。但我覺得兩個指標就是：有沒有感覺不適、還有能不能說出自己今天學了啥。如果一直寫自己舒適圈內的題目，那進步就會相對慢，我是覺得大概都要把自己維持在那種有些題一小時內是想不出來的感覺。而且很多時候我們會一直使用自己慣用的招數，但其實在討論區裡有很多更好的解法更好的招，所以這階段開始應該 coding 時間會慢慢減少，而花在思考跟看討論區的時間要相對增加。</p><p>這階段開始我也滿推薦去參加 leetcode 每週日的周賽的，周賽的結構大概是 1 easy + 2 medium + 1 hard 或是 1 easy + 1 medium + 2 hard，在這階段我覺得目標可以設在穩定三題。打比賽的那種全新題目 + 時間壓力我覺得是對於面試來說很好的經驗。更好的還是找個戰友，偶爾互相競爭一下賽後也可以互相討論。</p><p>最後一個階段就是開始挑戰 hard 題啦。當然我相信在第三階段大部分的人也會嘗試並偶爾可以解出 hard 題，但大概自己也知道如果是 hard 題在面試中還是多半不行的，所以這階段就是想辦法習慣 hard 題。如果說 medium 題是在熟練資結演算法的話，那 hard 題才是真正應用題的開始。在 hard 題裡你會看到更長或是更迂迴的題目敘述、或是多個資結演算法的組合拳、也有一些比較冷門的演算法。我自己大致把 hard 題分成經典跟非經典題，經典題還是講求熟練，很多 hard 題也是經典題的變形，非經典題部分我就不強求，先想辦法用非最優解寫出來再慢慢優化，遇到一題學一題。</p><p>在這階段其實我還是 medium、hard 一比一的比例刷，就算面試 Google 也不是真的題題 hard，撇除那些打比賽或是刷 codeforces 的選手本來就能秒 hard，剩下大家程度應該差不多（？）就是花時間啦，多會一題是一題。</p><p><strong>刷題的小撇步</strong></p><p>雖然總體來說我認為刷題就是付出程度（不只是時間，還包含思考）跟回報滿正相關的一種活動，但我還是有些自己偷吃步的小技巧。</p><p>1.題目描述判斷複雜度：</p><p>刷題多一點以後，慢慢開始可以根據題目描述或是面試官的各種暗示（？）來判斷複雜度。比如說有些題目一定得做遍歷才能拿到答案，那你就可以先排除 Binary Search 的方向了。或是題目能不能用動態規劃解通常也是比較明顯的（當然要真的寫出來有時候很難），早點確定方向就可以花更長時間想動態規劃本身要怎麼寫。這個小撇步主要還是靠經驗的累積。</p><p>2.測資大小判斷複雜度：</p><p>這個就是 leetcode 專用的，經驗上來說大概操作數量超過 10⁹ 就會超時，所以如果你看到 input 大小可以到 10⁵ 的話，那 log(n²) 的解法大概就可以放棄了，大概最佳解是 nlog(n)（為什麼不會是 log(n) 呢，因為 log(n) 的解法應該要能明顯到看得出來吧不需要這招）。這個是打比賽或刷題的時候好用，實際面試用不上的小技巧。</p><p>3.特定演算法的SOP：</p><p>很多人都會有這個，比如說實作 Binary Search 是要 while (low &lt; high) 還是 while (low &lt;= high) 最後要 return low 還是 return low+1，或是實作 BFS 的程式結構。</p><p>我自己的話就是 Binary Search、BFS、DFS、Dynamic Programming、Recursion 都有固定的寫法，固定寫法能去掉很多小失誤還有不確定因素，你當然會希望面試的時候能把大部分時間花在思考解法，而不是要預留時間想怎麼實作 DP，能準備的就要先準備起來。</p><p><strong>刷題策略</strong></p><p>很多人也會討論要怎麼選題目來刷，比如說按分類刷、按公司 tag 刷、隨機刷或是按難度刷。</p><p>前面大概有提到，我自己是按難度刷然後經典題為主，因為我覺得按分類刷的話好像會多一個提示，知道這題肯定得用 Queue 之類的。一般經典題裡討論區也會有很豐富的資源跟解法，對刷題初期我覺得幫助很大。有些新題可能討論區不太熱絡，最受歡迎的解法也不一定是最優的。</p><p>我覺得有一個可以提的是，每年開始面試的時候我是怎麼把之前的熟練度撿回來的。我大概是在 2019 年還在學生階段的時候每天花八小時度過了前三個刷題階段的，之後每年的首要目標就是先把熟練度撿回來然後繼續第四階段的 medium、hard 一比一刷題。一開始我就是跟我第一年的刷題策略差不多，但後來發現其實以前有沒有熟練的題目真的很明顯，不熟練的題目隔一年回來看就跟第一次見面差不多，熟練的題目沒必要再回來寫，所以我的策略就轉為先從不熟練題目列表開始解（感謝自己每年都有認真做筆記）。今年因為自己的不熟練題目列表好像已經沒那麼不熟練了，所以就從 Top 100 Liked 開始，我覺得這個清單滿不錯的，各種難度的題目分佈很健康。</p><p><strong>刷題的程式語言</strong></p><p>這個其實不太需要討論，我是什麼語言都可以派的。但確實，我第一年是用 Python 刷，後來花了點時間忍痛改成 C++。當時我是因為非本科 + Python 對於某些人來說好像是一種 red flag，所以第一年面試的時候即使我對資結演算法都挺熟的，還是在面試中常常被問到 python 的 list 或是 sort 是怎麼實作之類的，很煩XD 後來改成 C++ 後就很少遇到這種事了，除了 Google 外我相信大部分公司的面試官自己都是用 Java 或 Python 刷題。</p><p>一般來說可能前端工程師用 JavaScript 刷題外，大部分人就是 Python, Java, C++ 三種選擇。其他語言當然都可以，但 Leetcode 討論區可能就資源少一點。如果選擇 Python 的話，我是建議還是得多花點時間學習一下內部實作，很多人都是 list 跟 dictionary 一套用到底，但也可以多看看 Collections 內的一些資料結構。如果 C++ 一開始吃得進去的話我是覺得挺不錯的，沒有大家一開始想像的那麼可怕，上手後也常常從 STL（C++ 的內建 library）學到一些設計上的思想，會在某個階段突然有種打通任督二脈的感覺（咦）。</p><p><strong>要不要買 Leetcode 會員</strong></p><p>買！</p><p>我自己是每年都有買一個月啦。不過因為是一個月一千、一年五千，所以得自己拿捏一下要買一個月還一年。</p><p>我覺得我的心態就是，如果面試出了 leetcode 原題，我就要把握。一個月一千是不便宜，但每個月的不必要支出大概多數人都有超過一千XD 如果我今年的努力掛在一題會員才看得到的原題，那我應該會很不爽，所以就果斷買了。</p><h3>準備面試的心得與心態</h3><p><strong>連續面試了四年</strong></p><p>這邊我想說的是，雖然很多很猛的人可以應屆去 Google 工作，但網路上也常常可以看到很多人強調多數 Googler 都是面了不只一次的（依稀記得有個印像只有 10% Googler 是一次就面上的）。雖然冷凍期一年很煩，但也只有在應屆那年得失心會特別重，後來邊上班邊面試，一來你沒有太多心力準備跟煩惱，二來也沒有那種找不到工作的壓力。對我來說每年面試也是有好有壞，雖然固定要付出時間跟心力，但對於我這種剛畢業沒多久的人來說能有機會固定維持手感也不錯。很多網路上的文章也會強調，面試上了 Google 然後呢？進了公司後也要從菜鳥開始慢慢學習，有些人也把競爭的賽道從面試轉換到績效還是升職上，在工程師這條路上就是一直不斷的學習還有自我成長，進了想去的公司也就只能算是一個 checkpoint，沒進想去的公司也不代表自己以前沒成長或未來不會有成長。</p><p>還有一個是我自己很喜歡的發現，就是每年面試的過程中你可以感受到自己的成長是很明顯的，過了一次 phone screen 就不太擔心下次過不了，第三年能送 HC 第四年我開始面試的時候就有信心也能送 HC，每年都有種多走遠一點的感覺。總體來說我覺得雖然有些運氣成分，但 Google 面試比起其他家還是相對滿公正考察候選人的，至少不會有那種死得很冤的感覺。</p><p><strong>面試的運氣</strong></p><p>面了同一家公司四年，也可以看到有各種情況，比如說第一年跟面試官對不上頻率、第二年直接沒 headcount、第三年跟第四年面試難度差異巨大。雖然上一段才說相對公正，但不可否認的就是每年都有這些不確定性。</p><p>我覺得應屆生比較容易有那種雞蛋放同個籃子的感覺，比如說應屆沒上想去的公司就有世界毀滅感，但實際進入公司後你會發覺真的從公司內部都能看到太多亂七八糟的情況了，從公司外部根本就是純碰運氣。你從外面永遠不會知道這個 L4 職位主管是期待一個三年經驗的人還是八年經驗的人，你也不會知道這個職位是誰出錢的有沒有招人時間上的壓力，這方面我覺得就是盡人事聽天命，如果你有人脈可以打聽，多拿一些資訊永遠不吃虧，如果沒有資訊就投，投了就蓋牌不管，不要輕易有那種把一切都賭在一場面試的感覺。</p><p><strong>Google Taiwan 的難度</strong></p><p>這個我覺得是不可否認的，台灣 Google 就是比美國 Google 難，雖然我面試過程也有遇到美國的面試官，但就算是同一個面試官問同一題，台灣的職位就是比美國少很多，所以自然就競爭很激烈。</p><p>我也不是無憑無據的這樣講，我自己本身在台灣微軟跟美國微軟都工作過，確實台灣的門檻高很多，同事們跳槽去 Google 也是差不多這種心得。</p><p><strong>跟 Recruiter 合作</strong></p><p>網路上也很多人強調了這個，但我真的今年感受非常明顯，好好跟 recruiter 合作真的幫助很大。recruiter 可能有些不能告訴你的資訊，但他可以透過暗示或是默默幫助你，加上人家對於這家公司的面試經驗當然比我們多很多。不過跟 recruiter 合作當然也不是單方面的希望對方付出，我覺得自己要準備的部分就是好好搞清楚自己想要什麼還有遇到問題就發問，就是要給別人能幫你的空間。</p><p><strong>微軟面試官心得</strong></p><p>我自己在微軟也當過面試官，面 intern 跟正職都有，當過面試官之後看待面試的角度會不太一樣。比如說，對面試者來說這是你努力爭取來的一場面試機會，但面試官可能今天已經面了一整天八個人了，你剛剛面試前在緊張，面試官可能剛從一場會議出來，邊面試邊想等下還要寫面試報告。</p><p>很多人也提過，面試除了把題目解出來外，讓面試官覺得你是個不錯的同事也是很重要的，這部分大多就是體驗在溝通交流上，如果你有好好的花時間跟面試官討論，面試官也會覺得這場討論的體驗是比較好的，整體印象分數更好（？）其實也有很多時候面試官是想幫忙的，但常常沒有空間幫忙，所以好好跟面試官確認自己的解法跟疑問真的是很重要的。</p><p>正常的面試官也不會要求一定要每題 bug-free，誰工作上不是每週在產出 bug 的，重要的是寫出 bug 後的反應，慌不慌，解 bug 有沒有邏輯性。低級錯誤能不能靠自己找出來，複雜一點的錯誤能不能在提示下跟面試官一起解決。</p><p>-</p><p>如果覺得我的心得分享有幫助的話就幫我拍手分享囉。我也很樂意幫助更多人，如果有疑問的話留言我應該有時間會回。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9fa7f9717410" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Microsoft微軟工程師三年 — 如何上手新專案]]></title>
            <link>https://medium.com/@tobby168/microsoft%E5%BE%AE%E8%BB%9F%E5%B7%A5%E7%A8%8B%E5%B8%AB%E4%B8%89%E5%B9%B4-%E5%A6%82%E4%BD%95%E4%B8%8A%E6%89%8B%E6%96%B0%E5%B0%88%E6%A1%88-456010516c6e?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/456010516c6e</guid>
            <category><![CDATA[software]]></category>
            <category><![CDATA[computer-science]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[engineer]]></category>
            <category><![CDATA[career-development]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Sat, 31 Dec 2022 04:25:26 GMT</pubDate>
            <atom:updated>2022-12-31T04:25:26.418Z</atom:updated>
            <content:encoded><![CDATA[<h3>Microsoft微軟工程師三年 — 如何上手新專案</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C2n_a_x28-eAsgRsBDW5-g.jpeg" /><figcaption>微軟總部訪客中心</figcaption></figure><p>這篇文是接著「Microsoft微軟工程師三年」系列的第二篇，接著上篇 <a href="https://medium.com/@tobby168/microsoft%E5%BE%AE%E8%BB%9F%E5%B7%A5%E7%A8%8B%E5%B8%AB%E4%B8%89%E5%B9%B4-%E9%9B%9C%E6%83%B3-cd3dedce827">Microsoft微軟工程師三年 — 雜想</a>。感人居然還真有第二篇。</p><p>一直有個疑惑，當一個資深工程師（Senior / Staff 或以上）換組或換公司時，要怎麼快速的上手，在比較短的時間內開始發揮他應有的影響力及產出。不可否認的資深工程師會有很多 domain knowledge，常常我也在想一個資深工程師可轉移（transferable）跟不可轉移的知識應該要是怎樣的比例比較正常，但總之，可轉移並不代表可順暢轉移，所以這篇文我主要就是想探討怎麼順利的轉移這些能力跟知識。</p><p>雖然我不是個非常資深的工程師，但最近我自己也體會了來到一個新組或新專案後如何建立自己的知識體系並開始貢獻的過程：</p><p>最近換了一個組（上篇文提到的工作狀態轉變之一），到新組後本來的組長（tech lead）去了其他公司，加上種種原因後主管讓我接手當組長，有點臨危授命的意思，必須得要在比較短的時間內開始上手現有專案跟新專案（主管的期待是不必對全部細節都瞭若指掌，但是希望能夠保持組裡一定的基本運作，包括推進專案、維繫跨組合作、協調同事任務等等）。最近算是比較上軌道了（應該吧），所以記錄下最近的學習。</p><p>我整理了一下，覺得不管是上手新組或是新專案，不外乎就是這三個方向：系統與程式碼、業務邏輯、人際關係。</p><h3>系統與程式碼</h3><p>關於這一塊，需要宏觀（系統）來看也要微觀（程式碼）來看。個人認為從宏觀的角度來看更重要一些，目標是能在白板上畫出這個專案的流程圖，可以從抽象化或是 component（組件、元件）的角度來想，先從這個專案的 Input 是什麼 Output 是什麼開始，理論上設計的比較好的系統或專案應該會有比較乾淨的對外接口/介面（Interface），有了比較明確的切入點之後就是循線分析，慢慢的把邏輯理清楚。</p><p>在這個過程中，不需要花太多時間拘泥在細節上，我的想法是一層一層的由淺至深調查，從最粗的只知道專案的輸入輸出開始，往下一層看用到了哪些服務，而這些服務的輸入輸出是什麼，如果能夠兩三句話說清楚輸入輸出還有這個組件的職責，就可以到下一個組件了。而深入的程度，我覺得可以到聽得懂同事間討論的程度就差不多了，例如同事說我們要在哪個組件加功能，能夠回想起這個組件是個 key-value store、裡面裝的是哪種類型的資料，這樣就差不多了。</p><p>對系統有了宏觀的認識以後，下一步就可以開始著手閱讀程式碼了，畢竟最終工作內容還是在這些程式碼上修修改改添增東西。我的經驗是，雖然一個組或是一個專案可能會有很多組件，但是多數時候這些組件可能是現成的，比如說 Redis、Kafka、HDFS…，基本上對於這些現成組件需要熟悉的就是概念、怎麼設置參數、還有怎麼下指令來做運維（DevOps）操作，這個我覺得就比較像基本功，多看文件多動手操作，慢慢就熟悉起來了，而且這些知識就是能帶走能轉移的，累積到一定程度以後再進到新專案就沒這麼多陌生東西了。除了這些現成組件外，大多數的程式碼應該會放在集中的一些地方，可以透過查最近組裡的 PR 來找，從這裡開始熟悉。每個人看程式碼的習慣不太一樣，我的話主要會看兩項，一個是從程式的進入點（entry point 或是 main function）開始走一遍整個邏輯，另一個是程式的目錄結構，其實兩項都是為了幫助你對整個 codebase 有個粗略的認識，要寫程式的時候比較知道要寫在哪，可能需要一個特定方法（method）的時候也能知道這東西已經有同事寫過了。</p><p>微觀的部分，除了程式碼本身，週邊支援的生態系也很重要，比如說提交程式碼後 CI/CD 怎麼跑起來、編譯好的程式怎麼丟到生產環境的機器上、程式碼寫的 log 會發送到哪要怎麼查、有沒有隔離環境的資料庫可以玩、系統的監測要去哪裡找怎麼解讀等等。這部分其實我覺得願意花投入時間的話是非常加分的，因為待在這個專案比較長時間的同事也不一定有在這方面仔細看過，如果一開始便能成為某些特定領域的 go-to-person（就是其他人都會來問你這方面的問題）的話，不管是人際還是學習上都很有幫助。</p><p>寫完這段後自己發現以上很多都是以 backend/infrastructure 工程師的角度來思考的，又花了時間想了一下這些觀念要怎麼應用到前端工程師的專案中。生態系部分跟程式碼部分我覺得是差不多的，尤其前端的專案建置常常牽扯到一些 webpack 的黑魔法（黑箱？），還有一些平常不一定會特別花時間研究的細節，比如說前端的 routing、cookies 跟 LocalStorage 的使用、狀態管理的流程（redux 之類）、驗證相關的 SSO 還有 token 管理，都屬於實際開工後不一定會深入研究，但是了解過的人會有些優勢的「生態系」。而系統方面，雖然前端並不一定會像後端有職責非常明確分工的服務，但還是可以由淺至深的對現有元件展開調查，從最粗的每個頁面我們設計的 use case，到元件的輸入輸出（類似 React 的 Props 以及最後渲染出來的內容），目標一樣是能在白板上把這個網站的架構/流程圖畫出來。</p><h3>業務邏輯</h3><p>從我的觀點來說，業務邏輯跟人際關係滿高度相關的。</p><p>我覺得有幾個切入點，一個好的開始就是回答以下幾個問題：為什麼這個專案是由我們組來做、這個專案在商業上的目標是什麼、這個專案能帶給用戶什麼、用戶的使用場景是什麼。</p><blockquote>為什麼這個專案是由我們組來做？</blockquote><p>我覺得這個問題是最有趣的。在我的幾年工作經驗中，一個很有趣的發現是：大部分時候組與組之間的分界線是很模糊的，有時候你會發現一些職能跟你們組很相近的組，甚至讓你覺得這個專案他們做也可以。尤其是你們組的上游或下游，為什麼這個專案是切給我們做、為什麼不是其他組順手做掉，其實搞清楚這些問題能夠很大程度上幫助你了解這個專案的背景，也能讓你了解怎麼樣能幫助你們組成長（比如說未來要怎麼拿專案、怎麼幫主管爭取資源、怎麼跟合作組爭取時間等等）。你也可以在思考這個問題的時候多認識一點現有專案，了解你們組的優勢還有缺少的地方。</p><blockquote>這個專案在商業上的目標是什麼？</blockquote><p>如同我在 <a href="https://medium.com/@tobby168/microsoft%E5%BE%AE%E8%BB%9F%E5%B7%A5%E7%A8%8B%E5%B8%AB%E4%B8%89%E5%B9%B4-%E9%9B%9C%E6%83%B3-cd3dedce827">Microsoft微軟工程師三年 — 雜想</a> 中提到的，大部分工程師的工作就是「以現存的工程手段解決商業上的需求」，講白一點，沒有一個專案不是為了讓公司更賺錢的（不一定真的會賺錢，至少是老闆覺得這樣會賺錢）。所以對我來說，思考專案背後的商業目標就相當於思考我們為什麼需要這個專案，但又能夠給你一個更清晰的視野。對我來說，弄清楚這個問題有兩個好處，一個是能夠補齊你在工程之外你所需要的背景知識，對於理解前人程式碼（包含奇怪設計或技術債）或許能給你不同的觀點，且往後自己在做設計、判斷時能有更多判斷依據。另一個好處是遇到比較不清楚或是不太合理的需求時，更能夠跳脫出來思考這些需求跟商業目的的關係。</p><blockquote>用戶的使用場景是什麼？</blockquote><p>如果說思考商業目的是站在公司的角度來看專案的話，那思考使用場景就是站在用戶的角度來看專案。每個專案都應該會對用戶有一定程度的影響，但奇怪的是常常我們在做專案前或是做專案的當下都是看著 spec 滿足需求而已，並沒有想過這個專案對用戶來說是什麼。其實不管是站在公司的角度還是站在用戶的角度，我想強調的是 business sense 還有對商業的關心對於工程師也是很重要的，常常在工作中能遇到那種能夠提出非常突破（又很好）的解法的人，我覺得很多時候就是他們不拘泥於收到的需求，畢竟聽命於需求就會受限於訂需求的人的想法，如果能更有大局觀（？）的話則更有可能想出滿足真正需求的解法。</p><h3>人際關係</h3><p>最近越來越能體會，其實對於大部分公司或是主管而言，是更偏好 team player 而不是 10x engineer 的。當然不是說這兩個只能二選一，只能說很多時候做決定時要把這件事放在心裡，怎麼做才是個好的 team player。</p><p>人際關係簡單來說要認識人、還有認識人與人間的關係，然後又可以分成組內關係還有小組對外關係兩個部分。</p><p>在前兩個部分，<strong>系統與程式碼</strong>還有<strong>業務邏輯</strong>中，大概會對專案的上下游有一定的認識，而我們也知道在實際工作中並不是真的上游傳資料過來、下游按 doc 呼叫我們的 API，更多時候是跟上下游的同事們一起商量討論推進專案的。所以，對於上手一個專案或者是為之後鋪路而言，好好認識上下游的同事也是很重要的。簡單來說，對於每個合作組我會先想辦法找到一個 POC（point of contact），就是先把每個組對應到他們組上的一個人，簡單的跟他約個半小時時間互相認識下，也可以再稍微互相介紹下兩個組的系統（很神奇的是，常常兩個組合作了很久了卻對對方系統一點都不認識），打過照面後，以後出問題要找或是專案要合作感覺都比較有信心。</p><p>對於認識人與人間的關係這塊，當然也不是要提職場政治什麼的，但有時候還是多留心一點事情會比較順利。其中包含了哪個專案是誰負責、大家待在組裡的時間、大家的職級還有升級狀況、之前離職的同事… 等。總之就是，剛進新組換新專案，肯定就比較緊張並且想快速爭取同事信任、表現一番，但也不要太超過而弄成一個糟糕的開局。</p><h3>總結</h3><p>我覺得如果要簡單描述上手新專案/新組的過程，就是在建立知識索引（Index）。無論是程式、業務邏輯、還是人際關係，都是去想辦法知道「有哪些事是你該知道的」，並且把他們互相關聯起來。建立索引的目的不是要去一絲不漏的學會所有的事情，而是碰到不了解的事時，能夠有個切入點（entry point）知道要去哪裡找答案。</p><p>另外一點我想強調的是，很多人都提過了，如果剛進組或專案，就要發揮身為新人的優勢，為組裡帶來新的觀點跟經驗，認真找肯定能找到能有所貢獻的地方。一個很重要的點，有獲得但也盡量要付出，新人階段獲得遠大於付出是可以理解的，但是不能把同事的協助當成理所當然。慢慢找地方付出，當你發現你的付出大於獲得的時候，就代表你已經脫離新人階段啦。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=456010516c6e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Microsoft微軟工程師三年 — 雜想]]></title>
            <link>https://medium.com/@tobby168/microsoft%E5%BE%AE%E8%BB%9F%E5%B7%A5%E7%A8%8B%E5%B8%AB%E4%B8%89%E5%B9%B4-%E9%9B%9C%E6%83%B3-cd3dedce827?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/cd3dedce827</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[learning]]></category>
            <category><![CDATA[engineer]]></category>
            <category><![CDATA[microsoft]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Mon, 23 May 2022 15:52:58 GMT</pubDate>
            <atom:updated>2022-05-23T16:03:43.910Z</atom:updated>
            <content:encoded><![CDATA[<h3>Microsoft微軟工程師三年 — 雜想</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PnAxKXvH0jGalevNaKX_Tw.jpeg" /></figure><p>翻了一下，距離上次寫 medium 也是接近三年了，幾乎就是開始工作後就直接停更了。</p><p>工作三年，非常的忙，但也隨手在自己的記事本上記下了很多零散的心得，曾經試圖把一些想法組織起來放在自己的 Instagram，方便日後回來查看，不過 Instagram 終究不是一個太適合做筆記跟存放長篇文章的地方。</p><p>在考慮其他存放筆記平台的同時，也一直覺得自己還沒準備好把這些各式各樣處於不同階段的想法放在公開的環境，但同時也很感謝網路上的各種前輩們的經驗分享（有點資訊焦慮的我可以說是看遍了無數中英文的網路資源，手機裡的統計就看了上萬篇的技術文章或軟體工程師心得文），於是還是決定趁最近工作狀態有些轉變的空檔，搬運一些想法上來 medium。</p><p>從學生的身份到工作三年，在公司也從菜鳥變成了 Software Engineer 2，從沒有團隊合作和產品經驗到現在能執行 <em>溝通→設計 →實現 →測試 →部署→運維</em> 的一套完整流程，不管是對程式語言、軟體架構、工程實務、團隊合作、系統設計… 等能力都大幅度地進步了一輪。要寫的話其實每個方向都想寫上不只一篇（光是前陣子花上一週讀完的這篇 <a href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/">Performance Improvements in .NET 6</a>，就覺得對我的啟發超大，不吐不快），但超佛系更新的我也知道這不現實，寫一些經驗分享我也還不太確定怎麼拿捏界線，於是決定從我認為門檻比較低的開始，把一些沒有特定主題的雜想集結成一篇文章。</p><p>這篇雜想整理了我的筆記中一部分非技術相關內容，可以粗略地分成三種類別，一類是我對於軟體產業的觀察、一類是作為工程師對於生活周遭的想法，最後一大類是我認為優秀軟體工程師與一般軟體工程師的區別以及現階段我想努力的方向。有些內容會有點跳，主要是跟我的思維模式有關，很多時候我的思維都是比較發散並且做比較多聯想的。</p><h3>抽象化的能力</h3><p>關於抽象化能力之於工程師的重要性，很多文章都能看到，我尤其覺得 <a href="https://vgod.medium.com/%E8%BF%BD%E6%B1%82%E7%A5%9E%E4%B9%8E%E5%85%B6%E6%8A%80%E7%9A%84%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88%E4%B9%8B%E9%81%93-2022%E9%87%8D%E7%B7%A8%E7%89%88-7cccc3c68f1e">vgod 的追求神乎其技的程式設計之道</a> 特別精彩。</p><p>即使我對於抽象化這件事的描述可能沒法這麼精彩，我也想透過自身的例子來反映我是何時開始逐漸瞭解到抽象化對於電腦科學或是各種學科的重要性、以及在工作中我體會到了哪些抽象化的應用。</p><p>抽象化與模組化這兩個概念有很多共同點，對我來說他們都做了所謂的「封裝」，封裝在電腦科學乃至其他領域都隨處可見，我認為封裝能帶來兩個比較明顯的好處：</p><ol><li>幫助人類在有限的思考能力內（應該說是電腦跟人類有各自擅長的方向，人類更擅長擁有全局觀的高層次分析與思考，而電腦則不會漏了任何一件細節，而且計算超快不會失誤），將事情簡化來幫助理解。比如說我們可以封裝出一個在網頁前端做會員登入的方法，檢查瀏覽器有無 access_token 或 refresh_token，以此得出合法的 access_token 或者把用戶導向登入頁面。我們在設計業務邏輯時，就可以將流程簡化成這邊需要一個會員登入，而不是把這些複雜的細節加入高層次的設計之中。也就是 vgod 的文章里提到的，工程師們可以在不同的抽象層次上溝通並且根據需求切換到不同層次，在不同層次內不斷豐富討論細節。</li><li>透過達成共識的範圍切分，增加現代工程師協作軟體開發的能力。在學生時期自己寫程式還有在軟體公司工作的一大區別就是規模，一個人寫不出上千萬行的程式碼，但是一千個人可以，而在工程師規模擴大的同時，除了前後端對於接口要有清楚的定義跟切分，組與組之間或是任務與任務之間也需要有概念上的接口還有負責範圍的切分。藉由對職責的封裝，就可以避免很多程式新手的惡夢，改了A壞了B，修了B又壞了A，各組或是各人只需要保證滿足接口定義的行為即可。</li></ol><p>講了這麼久的封裝，再來談回抽象化跟模組化。對我來說，抽象化更多時候是垂直分層的，一般常見的比喻就是金字塔或是千層派，這個層級的概念可以是被制定標準的，比如說常聽見的 <a href="https://zh.wikipedia.org/zh-tw/OSI%E6%A8%A1%E5%9E%8B">OSI 網路七層模型</a>，也可以是根據不同人的不同觀點而異，我的一層可能是你的兩層。而模組化很多時候是在一個抽象層內透過職責區分不同的功能模組，也可以是跨越了多個抽象層的一個功能的群組，比如說手機的相機模組，同時包含了光學、電子電路、驅動、影像處理這些不同的層次，但是服務於同一個功能。</p><p>在寫程式的過程中，很多抽象化都已經在不知不覺間發生了，而這也是我覺得抽象化最神奇的地方，透過這些電腦科學家們以及工程師前輩們精妙的抽象化設計，一個程式新手可以在不懂編譯器、作業系統、以及 CPU 指令集的情況下，寫出滿足自己需要的 Python 程式並且成功運行。即使對於一個經驗豐富的工程師來說，也得利於抽象化而能夠只專注在自己想專注的領域，比如基於 <a href="https://grpc.io/">gRPC</a> 開發一套複雜的分散式微服務系統，也是建構在已經存在已久的 <a href="https://zh.wikipedia.org/zh-tw/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE">TCP 協議</a>之上，進而不用去煩惱網路掉包等問題。</p><p>在抽象化能力上，我覺得可以簡單區分成兩個維度：</p><ol><li>理解現存抽象層的能力：在公司與一個很資深的 IC（Individual Contributor，就是非管理職的工程師）聊天時他跟我提到，一個工程師的成長並不來自於他學了多少新技術或框架，也無關他寫了多少程式碼，而是這個工程師能不能時常問為什麼，以及是否時常花時間思考這些技術與框架背後的本質。 如果不能夠了解這個技術或框架在試圖解決哪個抽象層次的什麼問題的話，可能就會陷入無窮盡追逐新技術的循環。而理解了這個層次存在的目的、以及這個層次目前面臨有哪些難題、還有這個層次現存的解決方案後，學習新出現的技術就可以更快的做出一些觀念上的映射，也能夠有比起其他人更深入一層的理解。比如說我們從早期靜態的前端網頁發展過來，jQuery 解決了什麼問題，又為什麼需要像 knockout.js 這樣的資料綁定框架，而 React 的 virtual DOM 又為何崛起，以及怎麼最近 Next.js又談回 SSR（server-side rendering）。在有了一定的理解抽象層能力後，我認為這個工程師就有能力寫出更好的程式碼，即使還是寫寫簡單的 console app 或是爬蟲程式，也因為能夠注意到更多被隱藏起來的細節，而寫出更有執行效率或是更與想像一致的行為（很多程式的問題就是來源於「跟我想的不一樣」，我認為大多是因為不了解哪些細節被隱藏了所致），而很多時候遇到了 bug 或是沒見過的報錯內容，也可以在 troubleshooting 的過程中更容易定位到出現問題的層次以及解決的方案（起碼更知道怎麼下搜尋關鍵字！）。</li><li>設計抽象層的能力：雖然一般來說，一個軟體工程師的工作內容並不會跨越太多層級，但是在自己所在的抽象層內，也需要更細緻的抽象分層而達到好讀、好維護、好協作、不易出錯的效果。Android 開發的 MVVM、網頁開發的 MVC、常見的 <a href="https://refactoring.guru/design-patterns">design patterns</a> 如<a href="https://refactoring.guru/design-patterns/factory-method">工廠模式</a>或<a href="https://refactoring.guru/design-patterns/observer">觀察者模式</a>、時下很流行的<a href="https://azure.microsoft.com/en-us/overview/what-is-a-container/#overview">容器化技術</a>、還有<a href="https://blog.techbridge.cc/2019/06/21/actor-model-in-web/">從多執行緒計算到分布式計算最近又跑回瀏覽器渲染的 actor model</a>，我認為都可以想像成是長久經驗累積出來能滿足通用場景的分層設計。而做為最理解每間公司業務需求的工程師，選擇出合適的分層設計亦或是自己動手設計就是工作內容中很重要的一環。實務上我覺得還挺難的，常常受限於自己的經驗有限，設計出來的架構能滿足當下需求卻不能很好地服務未來產生的需求，而導致在不洽當的地方補上不洽當的程式碼，寫起來不太暢快也不好梳理邏輯。在設計軟體架構時，我覺得一個比較可以遵循的大原則是<a href="https://www.jyt0532.com/2020/03/18/srp/">單一職責原則</a>（Single responsibility principle），大模組負責一個大的邏輯上職責，而裡面的小模組負責小職責，單一方法則負責一個邏輯上不可分割的單元，當有一個模組沒辦法用一句話簡單的描述他的職責時，不僅代表設計上可能不適當，也代表跟同事溝通時無法確保對方跟你 on the same page。</li></ol><h3>對於運維的敏感度</h3><p>在閱讀 <a href="https://abseil.io/resources/swe-book">Software Engineering at Google</a> 一書時，裡面提到了一個聽起來很簡單但是卻啟發我很多的概念，也就是 Programming 跟 Software Engineering 的區別，主要就是對於程式生命週期的期望，Programming 關注的是怎麼寫出好程式，通常比較不在意後續怎麼維護這個程式，而 Software Engineering 之所以能變成一個工程領域，差別就在於這些為了延長軟體生命週期而發展出的各種工程實踐。書中提到，在就學時期，寫的程式基本上都活不過一個學期，而軟體公司發行的服務或軟體，基本上都是希望能夠無止盡的一直運行的。在 vgod 的其他文章中有提到，在 scale up 方面，要寫出一個長得像 Facebook 的網頁對於很多工程師來說是很容易的，但是要把規模擴大到幾十億的用戶就變成一件極其複雜的事情，我想這個道理也能應用在生命週期上，要寫出一個能運行五年十年的軟體，難度比起活一個學期的要難上不只幾十上百倍。很多人包含我，在上學時期寫程式時是不用考慮升級套件、函式庫的問題的，也有經歷過升級套件引發一系列連環報錯的惡夢，於是總是想著能運行得好好的則能不升級就不升級。而現實是，工作中很多時候會「被迫」升級套件，比如說選用的版本被通報了一個新的安全漏洞，或是隔壁組引用的套件依賴了一個新的版本等等。除了升級套件外，還得不斷的迭代新功能的同時不影響用戶（很多我們日常使用的產品如 Google Search 或 Facebook 都是一天不只部署一個新版本的！）、演練不曾發生過的流量高峰、並且開發團隊要能容忍人員的來來去去，於是乎，學會怎麼應對軟體在整個生命週期中遇到的各種狀況，就變成了一個非常重要且困難的議題了。</p><p>對於工程師而言，處在運維階段的時間（或是指程式跑在線上的時間）是處在開發階段時間的數百倍，而運維這個概念通常卻是出了學校之後才開始學習的，在上學階段根本很難想像 oncall（值班）的存在，至少對我來說至今仍然覺得像 Microsoft, Google, Facebook 等科技巨頭隨時都有上千個工程師守在電話前等待（？）問題發生是一件很魔幻的事。所以很可以想像的是，不同工程師之間對於運維的理解還有能力上的差距是非常巨大的，至少對我來說，能在設計或是開發階段就考量到日後運維難易程度的工程師就是非常優秀的。</p><p>我認為關於運維這件事，其實也是有個很完整的流程的， Google 也在運維這件事上發展出一個很完整的領域跟科學，稱為 <a href="https://sre.google/">Site Reliability Engineering (SRE)</a>。廣義上來說運維包含了很多領域，包含如何確保開發環境跟線上環境盡量一致（還記得那些在筆電上開發完部署到 linux 機器上的各種問題嗎？）、持續整合與持續部署（CI/CD）、軟體遙測（Software Telemetry，泛指如何在本地或遠端收集系統的運行指標跟日誌）、即時警報、壓力測試、紅白隊演練、供應鏈安全… 對於很多領域我也不是很了解，但我也想以自己比較有經驗的軟體遙測為例，舉出我對一些工程師運維能力方面的觀察。</p><p>我之前所在的團隊主要負責 Outlook/Exchange 後端系統分布在全世界幾十萬台伺服器上面軟體遙測的運行與管理，基本上業界流行的軟體遙測都是採用分層的設計，延時越小（秒級別或是小於秒級別）的遙測數據傳輸量越小，而延時越大（小時級別或甚至天級別）的遙測得益於錯峰、壓縮、批次處理的幫助能夠傳輸更大的數據量。並沒有哪種遙測數據更重要的說法，只能說是不同類型的遙測適合不同場景，延時小的數據往往需要更精準地事先定義好用途與採集方式，但能夠讓我們對於正在發生的事情有很快的應對，多作為報警用途，而延時大的數據則可以保留下更詳細的資訊，事後使用的彈性很大，多應用於分析的場景。</p><p>因為所有問題放到分散式系統裡都會變得很複雜，所以我先把軟體遙測舉例限縮在用筆電開發、部署到一台虛擬機上。最開始寫程式時，console.log() 或是 print() 總會淪為列印 Hello World 或是中間運算結果的工具，很少會考慮使用這些工具來幫助理解程式的實際運行狀況，甚至沒用過或見過 console.err() 或 Logger.Warning() 等方法。而很多人（對，這些都包含我）第一次的程式遙測經驗，大概就是寫好程式後興奮地部署到了線上，卻因為某種沒考慮到的邊緣情況（edge cases）程式異常地結束了，而因為在自己的筆電上沒法重現這個情況，於是在程式中加入了一些「檢查點」列印出當下的狀況，再透過 ftp、ssh 到機器上或是使用雲平台提供的交互介面查詢這些列印結果。這樣的行為其實比較容易導向一個負面的循環，也就是 <em>程式崩潰→加上日誌→發現問題→修復問題→新的程式崩潰</em>。而在軟體產業中，如此常見的程式崩潰當然是比較不能被接受的，一個擁有運維經驗和敏感度的工程師便能夠根據可能會發生的意外，配置好相應的遙測以及規劃意外發生時的自動（error handling 或 auto-healing）或人為處理流程。</p><p>在現在工程界一個比較常聽到的說法就是，永遠要預期每台機器、每個零件、每個程式、每個 system call 都有失敗的可能，我們的目標不是設計出一個完美不會失敗的系統，而是設計出一個有能力容忍失敗（fault-tolerant）的系統。在電腦科學的領域我們發展出了很多方法來避免單點失敗（<a href="https://data.leafwind.tw/single-engineer-of-failure-947e2ede1039">single point of failure，SPOF</a>，指的是系統裡的單一個模組故障導致整個系統故障），而對於身為工程師的我們來說，利用自身的經驗和素養來預期可能會發生的故障並針對其設計出更可以被運維的系統就是能力的一種展現。</p><p>在公司裡，其實也很常見遇到了錯誤才補上日誌的狀況，但也有時候能見到極度少見的錯誤被 CI 系統抓出來、系統異常後透過自我修復回到了健康狀況、或是看到某個很有經驗的工程師配置的日誌很完整的反映了數十萬台機器上一天只有幾台機器發生的問題，這時候就會不禁讚嘆對方在運維方面的能力，也會慶幸自己有這麼可靠的同事。記得有個主管就跟我說過（記不清楚原話了），大家都能寫出很漂亮跑得很好的程式，但遇到問題就是見真章了。</p><h3>我想像中的工程師</h3><p>有趣的是，我大學唸的是土木工程學系，畢業後大部分的同學也是做工程師，上學的時候教授也教我們怎麼當工程師。於是我時常在想，我現在做的軟體工程師跟土木工程師，在本質上有什麼相同點。其實這樣子的思考對我的幫助滿大的，我能夠在兩個比較熟悉的領域裡做一些概念上的比較，常常刷新我對於軟體工程師的認識。</p><p>以前總是聽到，工程師看重的是解決問題的能力。當然這句話我現在覺得不是這麼精確，因為設計師（平面設計師、工業設計師、產品設計師…）的工作本質上也是解決問題，但我覺得可以作為一個探討工程師的出發點。我想嘗試把工程師的工作定義為「以現存的工程手段解決商業上的需求」，而這個定義中不管是工程手段還是商業需求兩項都是不可或缺的。</p><p>在工作中有個很有趣的觀察，就是你不管丟給軟體工程師什麼問題，他都會嘗試用寫程式的方式來解決問題。如果有個相對不合理的用戶需求，大部分的工程師會透過設計出一個超級複雜的系統來滿足需求（所謂 over-engineering），但是同樣的問題丟給產品經理，則可能會通過與客戶溝通來把需求改的相對合理一些。我想表達的是，用工程上的手段解決問題自然是我們這群工程師的天性以及舒適圈，但作為一個商業公司的員工，也必須了解到沒有什麼系統是沒了商業需求後還需要存在的。</p><p>工程師中的「工」字，我覺得可以對應到工業界的「工」字，進而跟學術界做出對比。在工業界是相對沒有新東西的，很多人都說工業界在流行的「新技術」多半落後於學術界的研究十年或以上，但工業界有趣的地方就是他的目的是服務於人們的需求，應用的領域永遠是在第一線的、並且是更有彈性的。在 BYVoid 的這篇 <a href="https://byvoid.com/zht/blog/4-years-at-google-1/">在Google的這四年</a> 中也提到了這一件有趣的事，很多在學術界裡非常難以攻克的難題，在工業界可以被「降維」到能被解決的程度，因為在工業界是不需要證明一個做法能在任何情況下都有效，只需要在用戶會產生的情況下都有效就夠了。</p><p>在我的工作經驗中，沒有一個軟體工程師做的工作內容是獨一無二的，充其量都是「在我們公司的商業目的下也做一套跟別人很像的系統」，我寫 CRUD 他也寫 CRUD，我做搶購系統他也做搶購系統，我們搞一套分散式存儲他們也搞一套分散式存儲。沒有人是從零到有打造一套從未有人見過的系統（當然排除那些最最頂尖的工程師們），相對地，大部分人的工作都是找出現有的組件中適合自己公司場景的，拼湊出一個能用的系統，在上面添加一些業務邏輯，並隨著時間的推進慢慢的把這一套系統變成最適合自己公司的系統（但也只會有這麼一套系統，我們永遠也不會知道平行時空裡會不會自己已經架出了另一套更合適的系統）。在這樣的描述下，工程師的任務確實就是「以現存的工程手段解決商業上的需求」，而不會是研究出一套新的理論或是打造出一個跟商業無關的超強系統。也很直觀地，一個工程師認識多少現存工程手段（這個認識講的是比較深入的，包含這些手段的前提假設、長短處或是存在的目的）以及選擇手段的判斷力也就決定了這個工程師解決問題的能力。</p><p>在這篇講述領域設計（Domain-Driven Design）的文章 <a href="https://ithelp.ithome.com.tw/articles/10216645">關於 Domain-Driven Design 以及他的魅力</a> 中，這句話我覺得也能很好的描述工程師的本質：</p><blockquote><em>在這個時代，寫程式從來沒有那麼簡單過，但要解決的問題也空前的困難。</em></blockquote><p>很多經驗更豐富的工程師都會說現在是 <a href="https://www.bnext.com.tw/article/64982/low-code-and-no-code">low-code</a> 時代，在現在高階程式語言、雲服務、sever-less 概念、容器化… 的幫助之下，很多工程師可以在寫很少的程式碼或甚至只在網頁上拖曳物件就完成一個完整的系統（近半年我的很多工作都是在 <a href="https://azure.microsoft.com/zh-tw/services/data-factory/">Azure Data Factory</a> 上點點按按就完成的）。但這並不意味著現在當工程師就變得多簡單，雖然我們手中的工具進化了，但我們要解決的問題也隨著時代越來越複雜了。比如說開發一個 Android app，現在回頭去看七八年前的 app 多半會覺得看起來不太好用，但當時也不覺得不好，現在人們對於 UX（User Experience）的標準高了很多，Infinite List 導致手機卡頓、用戶資料在本地的加解密保存、畫面渲染阻塞了用戶互動等更複雜的問題都可能是公司用戶留存率下降的原因，也是我們透過更好用的工具解放了雙手後需要解決的核心問題。</p><h3>選擇的能力</h3><p>前面提到了工程師在日常工作中會花上很時間在做選擇，也提到了做選擇是需要相應的能力與經驗的。畢竟如果方案都定下來了，我不認為擁有五年經驗的工程師與二十五年經驗的工程師在執行力上會有太大的差距，但定方案這個行為本身，就會牽涉到這位工程師手上選項的多寡、對於需求的認識、過往的經驗、對未來的分析能力、還有判斷力。不好的系統設計很容易帶來越滾越大的技術債、處處可見的漏洞、特別易見的系統瓶頸、甚至是之後打掉重練的命運。而在一個設計良好的系統上工作，不僅可以很順利地在上面迭代新功能，還可以在出現問題時有更直觀的解決方案。</p><p>所謂選擇，就意味著沒有最佳解的存在，往往需要結合當下的時空背景才能做出判斷。工程師們很愛把「trade-off」一詞掛在嘴邊，就是你拿了什麼代價去交換的意思，我覺得一個很常見的例子就在資料/事件的流處理（<a href="https://medium.com/stream-processing/what-is-stream-processing-1eadfca11b97">stream processing</a>）上，如果不能忍受丟失資料可能就得忍受資料重複、如果不想丟資料又不想重複資料就要接受性能上的損失、如果要提升系統的吞吐量（throughput）就得犧牲每一筆資料的延時（latency），魚與熊掌。又或者是分散式系統裡面的 <a href="https://medium.com/%E5%BE%8C%E7%AB%AF%E6%96%B0%E6%89%8B%E6%9D%91/cap%E5%AE%9A%E7%90%86101-3fdd10e0b9a">CAP 理論</a>，想要資料的強一致性（strong consistency）就得在某些時候透過<a href="https://yuanchieh.page/posts/2020/2020-01-14_redis-lock-redlock-%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90%E8%88%87%E5%AF%A6%E4%BD%9C/">分散式鎖</a>或是 <a href="https://sauravomar01.medium.com/2pc-two-phase-commit-protocol-4ba47e441cdf">2PC（2-phase commit）</a>等機制讓系統退化到較少的並行度（Parallelism）跟可用性（Availability）、追求高可用性的話多半就得接受最終一致性（eventually consistency）對於用戶的影響。</p><p>剛進公司時還沒有太多參與或是主持 Design Review 會議的經驗（以及寫 Design Doc 跟做系統設計的經驗），網路上也很少相關的資源（很有趣，你能搜到上百篇如何做 Code Review 的文章，卻沒有幾篇如何做 Software Degisn Review 的文章），所以我也是慢慢摸索出怎麼做這些事，當然我的同事們也都是。前陣子在主管們的鼓勵之下我主持了一場橫跨多組的討論會，主題就是「如何做好 Design Review」。裡面有些怎麼做 Design Review、或者說怎麼做選擇的重點很值得參考：</p><ol><li>你的 Goal 與 Non-Goal：在開始設計階段時，如同前面關於抽象化所提到的，劃分範圍（Scope）是非常重要的。時常我們會以為我們對於這個專案的目標已經很明確了，但其實並不然。比如說我們知道這次專案的目標是引入 <a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-overview">CDN (content delivery network)</a> 來改善用戶在前端網站上的 <a href="https://developer.mozilla.org/en-US/docs/Glossary/Time_to_interactive">TTI（Time to Interactive）</a>，但是我們知道怎麼樣算是專案已經做完了嗎？TTI 降低了 20% 算達成目標了嗎？又或者我們網站 TTI 的主要瓶頸沒有辦法透過引入 CDN 來改善呢？關於制定目標有很多不同的方法論，可以設置具體的 use cases、預先定義量化指標… 這邊就不繼續探討。而 Non-Goal 也是一個我比較近期才接觸到的觀念，這邊放我一個搜尋到覺得不錯的定義，A potential goal or requirement which is explicitly excluded from the scope of a project. 定義範圍內要做什麼很重要，定義範圍內不做什麼也很重要，適度的限縮專案的範圍可以有助於之後做出更精確的決策，才不會陷入這也好那也不錯的窘境。</li><li>你的 Core Priorities：制定好專案的範圍後，釐清大家心目中的優先級這個步驟也是很必要卻很容易被忽視的，這個步驟會很大程度地影響到之後的考量點，比如說著重實現難易度的方案可能就會跟著重成本的方案截然不同，繼續上面引入 CDN 的例子，很多時候我們很難說明如果引入 CDN 會導致需要加上超級複雜的資料同步邏輯或是成本超級高是不是一件能被接受的事。而且比較尷尬的是很多「優先級」是不會被直接提出來的，但是卻又無形中影響著我們的決定，比如說人力（engineering efforts）或預算，很多時候我們選擇這個設計是因為對於我們來說實現相對簡單，或是使用的人月（people-month）是主管能夠接受的，但因為我們沒有在討論之初明確地提出對於人力的考量，而導致之後彼此之間有意見分歧時更難說服或理解對方。</li><li>你的 Stake Holders（利益相關者）：上面提到了做選擇之前得先釐清「大家」心目中的優先級，而這個「大家」又是個很模糊且容易有爭議的描述。有時候會遇到組員不理解組長做的決定、或是已經設計完畢到了實現階段才有人反對方案（經典 behavior question！）的情況，歸根究底都是因為沒有在適當的時間點找來適當的人一起討論。我認為在做選擇或是做 design review 時的一個重點就是討論，要是沒有來來回回而是設計者的所有提議都能被接受的話，就很難稱之為在做選擇。而討論的主要目的其實就是讓大家互相交流不同的想法和立場，讓我們在盡量擁有比較多資訊（包含前提假設、限制、前人的經驗、現有方案…）後再做出一個能基本滿足各方要求的決定。對於如何明確定義一個專案的 Stake Holders 以及適當的與會人員，我目前還沒有一個比較好的想法。</li></ol><h3>處處都是建模</h3><p>建模（modeling）對於我來說一直是一個很獨特並且也不斷在刷新的概念。我對於建模這件事的最初印象就是在撰寫後端服務的時候那些 model 資料夾底下的一個個 class 檔案（不知道大家有沒有跟我類似的經歷），學生時期跟著網路上的教學一步步做時，就曾遇到這樣的步驟去對應資料庫裡一張一張的表建立一個一個的 class 檔案，那時也並不知道這樣做的原因是什麼。而在學習 OOP（<a href="https://totoroliu.medium.com/%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-object-oriented-programming-%E6%A6%82%E5%BF%B5-5f205d437fd6">Object-oriented programming，物件導向程式設計</a>）的書本中也很常見 vehicle → wheeled vehicle → car → toyota 這樣的例子（其實我一直覺得這例子很爛，真的沒辦法幫助初學者理解寫程式時 OOP 扮演著怎樣的角色），也是很多人對於建模的初次探索。</p><p>隨著寫的程式和看的書越來越多，慢慢能夠體會建模這件事對於寫程式乃至電腦科學的重要性。如同前面提到的，寫程式的目的是為了解決在現實生活中的問題，而建模這件事就相當於銜接現實生活中存在的事物以及他們的行為和程式邏輯間的橋樑。如果今天我們想要模擬某條道路在不同車流量下的平均車速，我們不太可能只把一輛車當成 x, y 座標上的一個點，我們會希望給這個代表車子的物件（object）賦予他的長寬高，並且給他設定紅燈停綠燈行這樣子的行為，慢慢地，在我們追求模擬準確度的同時我們的程式也越來越貼近現實，有了根據不同時段而有不同模式的紅綠燈、有了限制不同的內外線車道、有了天氣等等外在條件，這就是建模。</p><p>我認為物件導向一部分就是指寫程式時用這種建模的方式來思考，而且很多時候我也覺得建模這件事跟 OOP 是相輔相成的。有時候我們在 OOP 程式語言的能力範圍內去做建模，並且我們的思維模式也被這些定義好的 OOP 模式（封裝、繼承、多型等等）引導，有時候人們對於建模這件事有了新的理解，OOP 程式語言也會在新的版本中加入新的功能/標準。因此，我覺得有時候光是在使用 OOP 程式語言如 C++, Java 或 C# 時，探索程式語言本身的標準都能夠提升自己的建模能力，並不是說每個語言看了看 for loop、if-else、宣告變數後就是熟悉了這門語言，很多時候去了解這些語言的設計方針才是更重要的。比如說當你在看 Java 裡面的這條繼承關係 Object → Collection → List → ArrayList，以及你看到 ArrayList 實作了 Serializable, Cloneable, Iterable, RandomAccess 這些介面後，透過比較 List 和 ArrayList 的相同相異之處或是比較其他實作了 RandomAccess 的類，也能去體會這門程式語言的設計者對於這些不同資料結構的想法。</p><p>建模這觀念有時候對我來說很具體，有時候又非常的抽象。具體的例子有很多，比如說電影劃位系統、Uber 即時顯示車輛位置等，我們嘗試把現實生活中實際看得到摸得到的東西嘗試用一些簡單的資料型別（string, integer, float 這種 primitive type）拼湊成一個電腦世界裡的物件，適當的抽取重要資訊並去掉雜訊（很多時候我們只在乎計程車在哪個路段上的哪個位置、不在乎他在哪個車道上或是這台車有多大），然後裝到適合它的地方（memory or disk, sql or no-sql）以利後面的運算或是資料傳遞。</p><p>抽象的例子則很有趣，有些東西我認為是電腦科學教了我怎麼去認識這個世界的，比如說在社群平台上，人與人的關係可以被表達成一個巨大的 <a href="http://alrightchiu.github.io/SecondRound/graph-introjian-jie.html">graph</a>（就是由節點與線組合成的資料結構，每個節點跟線都能有不同的屬性），當然我們可以從很多不同的視角來認識這個關係網絡，而 graph 只是其中的一種。在我學習 graph 之前，我可能也會有比較模糊的想法去認為人與人之間的關係可以連接成一種網狀結構，而認識了 graph 這種模型後，我可以更清楚的了解這些線段也能區分成單向的或是雙向的並且帶有不同屬性，認識了 GQL（<a href="https://neo4j.com/blog/why-database-query-language-matters/">Graph Query Language</a>）後，對於怎麼去有效率地走訪（traverse）一個人際網絡我也有了新的認識。其實這個過程就有點像是我「複用」了前人的思想結果。</p><p>有時候我們還得去對非常抽象的東西建模，比如說「用戶行為」。一個很常見對用戶行為建模的例子是當我們在前端使用 GA（<a href="https://medium.com/as-a-product-designer/ux%E8%A8%AD%E8%A8%88%E5%B8%AB%E9%81%8B%E7%94%A8google-analytics%E5%8D%94%E5%8A%A9%E7%B6%B2%E7%AB%99%E6%94%B9%E7%89%88%E7%9A%84%E7%B6%B2%E7%AB%99%E5%88%86%E6%9E%90%E5%B0%8F%E6%8A%80%E5%B7%A7-30c7589cd055">Google Analytics</a>，Google 提供用於追蹤網頁狀況和用戶行為的工具）或是開發類似系統時，很難以想像的是我們要怎麼把這些資料存起來，要有哪些 Columns 才能反映出這些行為，怎麼樣去設計能反映真實情況又能便於分析和展示？如果我們把每一次的頁面點擊行為記下一條 log，要怎麼在查詢時把一系列的操作關聯上？在這之上我們發展出了 <a href="https://hulitw.medium.com/session-and-cookie-15e47ed838bc">session</a> 的概念（當然你也可以說 session 是便於後端服務保留用戶資訊）來模擬用戶打開頁面到關掉頁面的一個用來裝一系列用戶行為的「容器」。還有其他例子像 Call Stack、DAG（Directed Acyclic Graph）、Pub-Sub（Publisher/Subscriber）都是我認為對抽象事物建模的展現。</p><p>在前面有提到，建模的過程中我們也會適當地去除雜訊，去除一些不需要特別關注的資訊，這個過程除了可以讓我們更專注在必要的資訊還有減少空間使用跟程式複雜度外，有時候還能有「奇效」。這邊講的奇效是我自己認為的，並不一定事情就是這樣子的。在我的觀察裡，當我們在建模的過程中只留下必要資訊後，我們在撰寫程式時會能夠比較容易地去「降維」或是「映射觀念」，你可能會發現很多我們想處理的事情在本質上是可以相通的，我們可以簡單的利用一些已知的觀念或解法來解決一個新出現的問題。如同我前面提到的 <a href="https://refactoring.guru/design-patterns">design patterns</a>，除了我認為這是一種前輩們歸納出來的常見抽象層設計外，同時我也認為這是前輩們歸納出來我們常遇到問題的解法。像<a href="https://refactoring.guru/design-patterns/observer">觀察者模式</a>可能早期就是從訂閱報紙這樣子一個比較簡單的現實生活中的行為開始慢慢演變，到現在應用的領域也越發地廣，像是 YouTube 訂閱這種比較高層次的設計、或是非同步（asynchronous）通知推播這種用戶體驗、消息佇列（<a href="https://medium.com/starbugs/%E8%AE%93%E4%BB%BB%E5%8B%99%E6%8E%92%E9%9A%8A%E5%90%A7-message-queue-1-de949e274c43">Message Queue</a>）這種中介層（<a href="https://www.redhat.com/zh/topics/middleware/what-is-middleware">MiddleWare</a>）的誕生、以及 <a href="https://nodejs.org/api/events.html">Node.js 裡面 events</a> 相關的實現。</p><p>在建模這件事情上，我一直覺得「購物車」是一個很有趣的例子，當思考到購物車這個功能時，很自然的會想到超市或是大賣場裡的那種購物車，我相信最開始的購物車功能也是這麼來的。慢慢地，網購世界裡的購物車開始發展出了自己的特色，並且有越來越標準化的趨勢。不管是在蝦皮、淘寶還是 Momo，購物車裡你都能夠很清楚的看到來自不同商家的商品被展示在一起，並且結帳時可以只選擇購物車內的部份商品，還會標記出正在舉辦不同優惠活動的商家，這些都是傳統購物車上看不到的功能。到了現在基本上不管訪問了哪個電商網站，都不需要去學習購物車如何使用，也不太會發現哪家的購物車有什麼特別不一樣的功能，我覺得這就有點像我們見證了一種經典的設計模式的誕生。</p><h3>軟體的假設</h3><p>前面有提到，很多在學界相對困難的問題，經過一些「假設」或「限制」之後在工業界能夠變成簡單很多的問題。因此在我們開發一個軟體或一個系統時，前提假設幾乎是無處不在的。就像在進行<a href="https://www.educative.io/courses/grokking-the-system-design-interview/m2ygV4E81AR">系統設計面試</a>時一樣，我們可以很輕易的在面試中假設某資料的讀寫數量比是 100:1，從而讓我們的設計更適合這個題目的目標。在現實的開發過程中，更多時候前提假設不是這麼明顯的，當然我們還是會有很多來自自己或是同事們的假設，比如說預期的用戶數量或是 QPS（Queries Per Second，每秒查詢數量），但很多人容易忽視或是沒搞明白的就是我們所依賴的各種輪子（套件、中介層、雲服務）所引入的限制跟前提假設。比如說當我們引入了一個分散式的 Document Store 雲服務如 <a href="https://aws.amazon.com/tw/documentdb/">Amazon DocumentDB</a> 或 <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction">Azure CosmosDB</a>，我們只要寫少少幾行程式碼就可以享受雲平台所提供的高擴展性（Scalability）及高可用性，但我們卻可能因為沒有仔細閱讀過服務本身提供的資料一致性保證（Data Consistency Guarantee）而導致有 Data Quality 問題時無從下手，或者是沒有研究過該服務的 Performance Best Practice 而遇上本不該遇到的效能瓶頸。</p><p>雖然很難做到，但很多時候我會認為搞清楚使用工具的前提假設是一件本來就應該要做的事，畢竟我們就是要用這些工具做事的人。而我認為真正困難的是，當一個假設改變之時，我們是否有能力做出相應的改變。這樣子的描述可能有點不好理解，我嘗試舉一個例子：早期的數位相機和手機，我們能看到在每年的發表會中越來越大的感光元件、光圈和鏡頭尺寸，而從某一個時刻開始，各大廠商的努力重心慢慢的不在這些硬體上了，我們有了透過傳統演算法或是機器學習演算法而實現的夜拍模式或是實時調色，我認為這些技術其實一直都是存在的（至少以前我們也會把數位相機拍的照片導入到 Photoshop 調一下），但是受限於早期晶片的運算能力，在以前這些技術是不太能作為一個選項放到手機裡的。一位厲害的工程師應該要能很清楚這些前提假設與設計決定之間的因果關係，並且時不時重新審視這些假設，當發現了一個我們依賴的假設發生變化時，能夠有能力思考那些曾經被封印的選項。我相信大部分的工程師，如果過去三年都在努力改進鏡頭的能力，那大概第四年也會這麼做的，即使這已經成為了一個相對吃力且沒效率的選項。</p><p>剛剛提到除了工具的前提假設之外，還有我們自己設置的假設，對於這類型的假設我覺得沒有什麼不好，畢竟我們總要先有個起頭，有了這些初始條件後確實能幫助我們更快地做出軟體的初始版本。不過在之前我自己開發的經驗來說，其實這些假設通常是很難準確的，就像肯德基大概沒想到蛋塔會這麼受歡迎之類的（誤），很多時候用戶的行為會跟想像的差別甚大，所以我覺得適當地引入 Feedback Loop 跟 Data-Driven 是很重要的，當我們的服務上線之後，用戶其實也在不斷地用他們的行為教導我們他們喜歡怎樣的體驗，常常我們會說要以用戶為核心，卻不斷地忽略這些明顯的訊號。</p><h3>便於人類的設計</h3><p>這個段落我還沒想得很清楚，就是一些我覺得有趣的觀察。</p><p>在寫程式時，偶爾能發現一些對於電腦相對沒意義卻明顯是便於人類的設計，比如說根據不同用途切分出不同的程式碼檔案，其實很多時候在編譯階段也是被合成回一份大檔案（像是 C 語言裡的巨集），但是透過不同的文件夾結構以及檔案命名，確實能很大程度地幫助我們找到想找的程式碼片段和組織我們的思考。</p><p>或者是所謂的可讀性（readability），也是指給人類去讀而非機器去讀，可讀性我認為可以分成程式碼的可讀性或是資料的可讀性。程式碼的可讀性通常會跟程式碼風格（Coding Style）扯上關係，變數和函數的命名或是空白鍵和 Tab 之爭其實對於最後編譯出來機器碼沒有什麼區別，但是確實會很大程度地影響工程師之間協作的生產力，Google 內部還有所謂的 <a href="https://news.ycombinator.com/item?id=22620455">Readability 認證</a> 來確保線上運行的每一行程式碼都有一定的程式碼風格上的把關。資料的可讀性這件事我也是最近才比較有感覺，以前在開發簡單的網頁前後端的時候大部分交換資料的格式就是 JSON，到了最近有經驗跟 Avro、Parquet、Protobuf 這些資料格式打交道，才體會到 JSON 或是 XML 這種格式用比較大的存儲空間換來的資料可讀性和彈性的好處。像 Protobuf 這種對儲存空間大幅度優化過的格式直接打開是肉眼看不懂的 binary 形式的，需要另外消耗一些計算資源才能夠轉換成人類看得懂的資料，這其實對於 Debug 或是設定檔這種講求方便或是對存儲空間不敏感的場景來說滿不方便的。關於這些不同資料格式之間的比較，我個人很喜歡 Design Data-Intensive Application 這本「聖經」中的介紹，這裡也有 <a href="https://www.jyt0532.com/2019/01/24/encoding-and-evolution/">jyt 前輩的心得筆記</a>。</p><p>其實這些便於人類的設計，大多都是犧牲了一點不管是計算上還是存儲上的效率來換取人類的方便（其實另一個角度來說也是增進了開發時的效率），而這些犧牲也時常是可以被優化掉的，像是編譯時額外付出的計算資源就是一次性的，不需要每次程式運行時都付出這筆代價。</p><h3>小結</h3><p>呼，好不容易寫了一個段落。</p><p>有時間的話可能會再上來修修改改，也繼續想想之後想寫的主題。</p><p>雖然說這篇文章主要就是一個自我紀錄用途，但如果有人能看得開心也很好，也歡迎能夠一起留言交流意見，我也滿希望可以在這些主題上找到能一起討論的夥伴。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cd3dedce827" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[用 python 實作 Binary Heaps (Priority Queue)]]></title>
            <link>https://medium.com/@tobby168/%E7%94%A8-python-%E5%AF%A6%E4%BD%9C-binary-heaps-priority-queue-12e0b82ed7b3?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/12e0b82ed7b3</guid>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[computer-science]]></category>
            <category><![CDATA[data-structures]]></category>
            <category><![CDATA[priority-queue]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Mon, 19 Aug 2019 16:54:23 GMT</pubDate>
            <atom:updated>2019-08-19T16:54:23.895Z</atom:updated>
            <content:encoded><![CDATA[<p>距離上次的 <a href="https://medium.com/@tobby168/%E7%94%A8python%E5%AF%A6%E4%BD%9Clinked-list-524441133d4d">用python實作linked-list</a> 已經半年了😂 （雖然中間有更新其他文章）</p><h3>什麼是 Priority Queue</h3><p>Priority Queue 比較常聽到的翻譯是「優先權佇列」，這裡的 Queue 概念與資料結構中的 Queue 我認為是滿相似的，一般來說 Queue 是「先進先出」（first-in-first-out）的，而 Priority Queue 也是指多個資料放入後，<strong>只能以特定的順序來一個個存取出來</strong>。而這個特定的順序，即是 Priority（優先權）的概念，可以是值的大小、花費的成本、距離截止的時間等等。</p><h3>Binary Heaps 簡介及特點</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*itoXsdHr0LYMEzuozilyEA.png" /></figure><p>在實作 Priority Queue 時，經常會使用 Binary Heap （二元堆積）來達成，Binary Heap 有以下幾個基本定義：</p><ol><li>Binary Heap 顧名思義每個節點都最多只有兩個子節點。</li><li>若為 Max Heap，每個母節點都比他的兩個子節點還要大；Min Heap 則相反。</li><li>Binary Heap 可視為一種 Complete Binary Tree，也就是說，每層填滿後才可以把節點放入下一層，而每層又是從左至右填入。</li></ol><p>而為什麼要以 Complete Binary Tree 的形式來儲存 Heap 而不是一般的二元樹就可以呢，主要是 Complete Binary Tree 能夠使用一些小技巧來大幅的降低我們在實作 Heap 上的難度以及所使用的空間（實作上是不需要 left, right 的指標的），這個部分後面會詳細的說明。</p><p>看懂 Binary Heap 後，我們就可以來看看它常見的兩種操作：</p><ol><li><strong>Insert</strong>: 插入新的 node 後，將樹調整回每個節點都比他的兩個子節點還要大的狀態。</li><li><strong>Extract</strong>: 取出帶有最大值的node後，將樹調整回每個節點都比他的兩個子節點還要大的狀態。</li></ol><p>而這兩個操作的時間複雜度都是 O(logn)，這部分也是後面實作時會說明。和一般的陣列相比，雖然陣列插入元素的速度是O(1)（直接放在最後面），但找到陣列中的最大值則需要O(n)的時間（需要遍歷整個陣列），而若要取出最大的值又需要將後面的資料往前搬移，因此，需要考慮使用場景再來選擇適合的資料結構。</p><p>註：有種名為 Heap Sort 的排序方法就是將資料依序放入 Heap 內（nlogn），再將資料一個一個取出（nlogn）， 所以此種排序法的時間複雜度也是O(nlogn)。</p><h3>Complete Binary Tree to Array</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z7BkDTZh7ccShFtHYo0Dtw.png" /></figure><p>再來這個是我以前第一次聽到的時候覺得超級酷的小技巧。上圖 complete binary tree 中的編號是節點被放進樹中的順序，而我們仔細觀察可以發現：<strong>每個子節點的數字都是他的母節點數字的兩倍或是兩倍加一。</strong></p><p>而又因為 complete binary tree 一定要填滿一層才可以進入下一層，所以在中間是不會有數字被跳過的。</p><p>所以，我們可以把一連串的陣列想像成是 complete binary tree，第一格（array[0]）不算，之後如圖中所示，array[4] 的左節點是 array[8]，而右節點是 array[9]。而<strong>若要這個陣列成為一個 heap，則要滿足所有 array[n] 皆大於 array[2n] 與 array[2n+1]。</strong></p><p>在這樣的做法下，我們便可以省下左右指標的空間，且在 heap 的操作上，只要將節點的 index 除以 2 就可以找到他的母節點所在位置，超級方便。</p><h3>實作概念</h3><p>在我實作 maxPriorityQueue 這個類別時，主要有兩個屬性：heap 是代表這個二元堆積的陣列，而 length 則是堆積中的元素數量。（記得 heap[0] 要先塞東西）</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9beaa4425bceab3ce5f50dd73bda4a19/href">https://medium.com/media/9beaa4425bceab3ce5f50dd73bda4a19/href</a></iframe><p>而前面我們有提到，不管是在 insert 或是 extract 的過程中，我們都需要將 Heap 調整成符合條件的排列順序，這邊會用到兩個特別的函數：swim 以及 sink。</p><p>swim 指的是向上游，我們先將新的元素放在 heap 的最後面，若這個元素比他的母節點還要大，就跟母節點交換，直到沒有母節點或是比母節點小。</p><p>而 sink 就是向下沉，我們先將 heap 的根節點（整個 heap 中的最大值）與最後一個節點交換後，就可以取出最大值，之後再將被換到根節點的點往下沉，若有子節點比這個點大，就交換，若兩個子結點都比他大，就跟比較大的那個換。直到沒有子節點或是比所有子節點都大。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c45df38d8169532f1ca599978b0cdf57/href">https://medium.com/media/c45df38d8169532f1ca599978b0cdf57/href</a></iframe><p>這樣子就完成 Priority Queue 的實作了，在使用上可以這樣用</p><pre>pq = maxPriorityQueue([1,6]) # 建立一個有 1, 6 的 PriorityQueue</pre><pre>pq.insert(2) # 插入一個值為 2 的點</pre><pre>max = pq.extractMax() # 取出最大值</pre><pre>print(pq.heap) # 如果想看到操作情形，可以直接印出目前的 heap </pre><p>而在上面的實作中，有兩個地方我比較想提到的：</p><ol><li>在 maxPriorityQueue 的 __init__() 中，除了必要的 self 外，還加入了 array = []，是因為 python 中並沒有像是 C 或 Java 等語言有 overload 的用法，因此就讓 maxPriorityQueue() 預設是傳入一個空陣列。</li><li>像是 __swim() 等前面有兩個底線的函數，也像是 C 或 Java 等語言中的 private 作用，能夠限制函數只能在類別中被呼叫，並沒有辦法用 pq.__swim() 這樣的語法來呼叫。（但也有一些方法可以叫啦…）</li></ol><h3>Priority Queue 應用</h3><p>Priority Queue 的應用場景相當明確，大部分都是需要特定順序來取用資料或者是只需要前幾大的資料時，例如在作業系統中的 Scheduler 挑選適合的工作並載入到記憶體中準備執行、或者是這個用戶最常使用的十個聯絡人、甚至是非常有名的 Dijkstra’s algorithm （用來安排最短路徑或是最少花費）都能夠使用。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=12e0b82ed7b3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[機器學習馬拉松（一）— EDA、標準化、相關係數、KDE、離散化]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@tobby168/%E6%A9%9F%E5%99%A8%E5%AD%B8%E7%BF%92%E9%A6%AC%E6%8B%89%E6%9D%BE-%E4%B8%80-eda-%E6%A8%99%E6%BA%96%E5%8C%96-%E7%9B%B8%E9%97%9C%E4%BF%82%E6%95%B8-kde-%E9%9B%A2%E6%95%A3%E5%8C%96-4cf9de9b239c?source=rss-b9b41258e57b------2"><img src="https://cdn-images-1.medium.com/max/1014/1*thVhemUID17Q73e4kTvkzA.png" width="1014"></a></p><p class="medium-feed-snippet">&#x672C;&#x7BC7;&#x6587;&#x7AE0;&#x7684;&#x5167;&#x5BB9;&#x5927;&#x6982;&#x662F;Day1&#x5230;Day16&#xFF0C;&#x4E5F;&#x5C31;&#x662F;&#x7B2C;&#x4E00;&#x500B;&#x6578;&#x64DA;&#x524D;&#x8655;&#x7406;&#x7684;&#x968E;&#x6BB5;&#xFF0C;&#x4F46;&#x5176;&#x4E2D;&#x5F88;&#x591A;&#x662F;&#x4ECB;&#x7D39;&#x5957;&#x4EF6;&#x5982;&#x4F55;&#x4F7F;&#x7528;&#x6240;&#x4EE5;&#x6211;&#x5C31;&#x8DF3;&#x904E;XD&#x3002;&#x4E3B;&#x8981;&#x6311;&#x4E86;EDA&#x3001;&#x6A19;&#x6E96;&#x5316;&#x3001;&#x76F8;&#x95DC;&#x4FC2;&#x6578;&#x3001;KDE&#x3001;&#x96E2;&#x6563;&#x5316;&#x9019;&#x4E94;&#x500B;&#x4E3B;&#x984C;&#x4F86;&#x8AC7;&#x3002;</p><p class="medium-feed-link"><a href="https://medium.com/@tobby168/%E6%A9%9F%E5%99%A8%E5%AD%B8%E7%BF%92%E9%A6%AC%E6%8B%89%E6%9D%BE-%E4%B8%80-eda-%E6%A8%99%E6%BA%96%E5%8C%96-%E7%9B%B8%E9%97%9C%E4%BF%82%E6%95%B8-kde-%E9%9B%A2%E6%95%A3%E5%8C%96-4cf9de9b239c?source=rss-b9b41258e57b------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://medium.com/@tobby168/%E6%A9%9F%E5%99%A8%E5%AD%B8%E7%BF%92%E9%A6%AC%E6%8B%89%E6%9D%BE-%E4%B8%80-eda-%E6%A8%99%E6%BA%96%E5%8C%96-%E7%9B%B8%E9%97%9C%E4%BF%82%E6%95%B8-kde-%E9%9B%A2%E6%95%A3%E5%8C%96-4cf9de9b239c?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/4cf9de9b239c</guid>
            <category><![CDATA[statistics]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[data-visualization]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[機器學習]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Thu, 09 May 2019 06:01:56 GMT</pubDate>
            <atom:updated>2019-05-09T06:06:38.089Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Bloom Filter 介紹以及python實作與測試]]></title>
            <link>https://medium.com/@tobby168/bloom-filter-%E4%BB%8B%E7%B4%B9%E4%BB%A5%E5%8F%8Apython%E5%AF%A6%E4%BD%9C%E8%88%87%E6%B8%AC%E8%A9%A6-736aea241679?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/736aea241679</guid>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[data-structures]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Sat, 06 Apr 2019 17:35:20 GMT</pubDate>
            <atom:updated>2019-04-06T17:35:20.933Z</atom:updated>
            <content:encoded><![CDATA[<p>前幾天看到了科技報橘的這篇文章「<a href="https://buzzorange.com/techorange/2019/03/27/3-important-data-structure/">想當資深工程師？你需要熟悉這 3 種資料結構</a>」，看完之後覺得Bloom Filter這個結構相當神奇，不僅設計得巧妙、也確實可以在特定情況下非常快速的過濾資料O(1)、同時使用固定的空間O(1)。因此，查了一些資料後，便用python做了簡單的實作及測試。</p><h3>Bloom Filter 介紹</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xD6d6yj2tNlMLMxU5A3teA.png" /><figcaption>圖片來源：<a href="https://www.abhishek-tiwari.com/bloom-filters-is-element-x-in-set-s/">https://www.abhishek-tiwari.com/bloom-filters-is-element-x-in-set-s/</a></figcaption></figure><p>總歸來說，Bloom Filter是利用雜湊函式（Hash function）以及一連串Boolean陣列來實現的結構。資料被加入Filter時，先經過hash function得到一組數字，再mod陣列長度後將該位置設定成True，因此可以確保同一筆資料再度經過一樣的hash function，會得到同一個位置。也就是說，將資料經過指定的這些hash functions，如果每次指到的位置都是True，則可以推定他有極高的機率是我們需要的資料，但也會有非常少數「運氣好」的資料通過測試。</p><p>因此，Bloom Filter大概有以下幾個特性：</p><ol><li>插入資料的時間是O(1)，即是運行固定數量hash functions的時間，但須確保hash function的運算時間也是O(1)。</li><li>需要的資料保證不會被誤判，但不需要的資料有一定機會被誤判（即為Fail negative, Fail to detect something.）。但誤判的機率可以用陣列長度以及hash function數量來控制。</li><li>使用的空間固定，而且對比資料量來說算是滿小的。</li></ol><p>因此Bloom Filter的使用情境應該是在資料量極大、可以接受部份不需要資料被誤判的情況下，換取相當的時間與空間。上面的文章就提到了Gmail用來判定垃圾信件，或者我在<a href="https://medium.com/@Kadai/%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B%E5%A4%A7%E4%BE%BF%E7%95%B6-bloom-filter-58b0320a346d">另一篇文章</a>看到的，交友軟體用來避免配對到已經看過的對象。</p><p>而我也有讀到另一個資料結構Counting Filter，算是Bloom Filter的改良版。由於Bloom Filter的陣列中只有True和False，因此我們無法在Filter中移除某一個資料，而Counting Filter就是讓陣列記住被設成True的次數，因此就可以移除一筆特定資料，但使用的空間就會增加。</p><h3>Python實作與測試</h3><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2744ad7191778c201fbf8c3658f96bb1/href">https://medium.com/media/2744ad7191778c201fbf8c3658f96bb1/href</a></iframe><p>在init的階段呢，可以設定陣列的大小以及hash functions的數量（最多五個），hash function則是使用python內建的hashlib。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/867b506096996dcd36fc1a2c23fc0618/href">https://medium.com/media/867b506096996dcd36fc1a2c23fc0618/href</a></iframe><p>而add_data與is_exist方法基本上是相當類似的，要注意的是需要將輸入的string編成utf-8。而hash function的結果要使用hexdigest()方法取得。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/42c7c4fffb54a338aa54b12d7ab0bc2d/href">https://medium.com/media/42c7c4fffb54a338aa54b12d7ab0bc2d/href</a></iframe><p>test的方法主要是要測試fail negative的比例，我把理論值直接使用True在陣列中的比例的hash次數次方，而實驗值就是一百萬筆測資的結果，測試多次發現誤差相當的小。</p><pre>There are 258 True in the 1000 bit.<br>With 3 hash functions, calculated false negative rate is 1.71735%.<br>In 1000000 test cases, the false negative rate is 1.72330%.<br>This test takes 12.74s</pre><p>完整的程式碼我放在<a href="https://github.com/tobby168/data-strustures-implemented-by-python/blob/master/BloomFilter.py">我的Github上</a>。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=736aea241679" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Use id() to understand list and immutable in Python]]></title>
            <link>https://medium.com/@tobby168/use-id-to-understand-list-and-immutable-in-python-56492509bda3?source=rss-b9b41258e57b------2</link>
            <guid isPermaLink="false">https://medium.com/p/56492509bda3</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[programming-languages]]></category>
            <dc:creator><![CDATA[Tobby Kuo]]></dc:creator>
            <pubDate>Fri, 22 Feb 2019 15:33:24 GMT</pubDate>
            <atom:updated>2019-02-22T15:33:24.132Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Python id() function</strong></h3><p>Here is the explanation of id() in w3schools:</p><p>The id() function returns a unique id for the specified object. All objects in Python has its own unique id. The id is assigned to the object when it is created. The id is the object’s memory address, and will be different for each time you run the program.</p><p><a href="https://www.w3schools.com/python/ref_func_id.asp">Python id() Function</a></p><p>From the explanation above, there are somethings we need to notice:</p><ol><li>It’s important to note that <strong>everything in Python is an object</strong>, even integers and Classes. So if you run id(123) you will still get an integer represent its id.</li><li>The integer returned by id() represents an object’s memory address, so we can use this integer to <strong>check if two object point to a same address</strong>.</li></ol><p><a href="https://stackoverflow.com/questions/15667189/what-is-the-id-function-used-for">What is the id( ) function used for?</a></p><h3>Python list object and id()</h3><p>The list object in python is very similar to array in other languages. They all have some methods liked insert, pop, remove, index …etc. You also can read and write a specific position in list with constant time. But if you use id() function to look up items in list, you’ll find the biggest difference between list and array:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YCE4vneSAGyrAspjQ-LsqQ@2x.jpeg" /></figure><p>The address of items in list are not continuous! A usual Array use continuing positions in memory to store data, but a list in python doesn’t.</p><p><strong><em>Actually, a list in python is implemented by an arrays of pointers which point to the list elements.</em></strong></p><h3>immutable</h3><p>Everything is an object in python, and each have these attributes:</p><ul><li>identity: To represent the memory address for this object. You can get the identity from the id() method mentioned above. Once an object is created, you can’t change its identity.</li><li>type: To represent the type of this object. You also can get it from the type() method. Again, Once an object is created, you can’t change its type.</li><li>value: To represent the data or the value in this object. Different from identity and type, some object’s value can be changed. We call these kind of objects ‘mutable’, and ‘immutable’ for those can not change value.’</li></ul><p>So, what common types are mutable and immutable in python?</p><p>immutable objects:</p><ul><li>Numeric types: int, float, complex</li><li>string</li><li>tuple</li></ul><p>mutable objects:</p><ul><li>list</li><li>dict</li><li>set</li></ul><p>We can show that list is mutable by using id():</p><pre>&gt;&gt;&gt; a = [0, 1, 2]<br>&gt;&gt;&gt; id(a)<br>4472721480<br>&gt;&gt;&gt; a[2] = 3<br>&gt;&gt;&gt; id(a)<br>4472721480<br>&gt;&gt;&gt; a<br>[0, 1, 3]</pre><p>Or show that int is immutable:</p><pre>&gt;&gt;&gt; a = 1<br>&gt;&gt;&gt; id(a)<br>4470587440<br>&gt;&gt;&gt; a = 2<br>&gt;&gt;&gt; id(a)<br>4470587472</pre><p>reference: <a href="http://wsfdl.com/python/2013/08/14/%E7%90%86%E8%A7%A3Python%E7%9A%84mutable%E5%92%8Cimmutable.html">http://wsfdl.com/python/2013/08/14/%E7%90%86%E8%A7%A3Python%E7%9A%84mutable%E5%92%8Cimmutable.html</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56492509bda3" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>