Total Pageviews

2020/04/01

[閱讀筆記] Learn to Think in Systems (4/7)

  1. Delays 分成三種類型 (以零售業為例):
Types
Description
Perception Delay 
(認知延遲)
是主觀認知,通常在銷量變化初期不會立即作反應,而是分析過去一段時間內的資料來判斷是否為真實趨勢或只是短期波動
Response Delay 
(反應延遲)
當趨勢明確需要調整訂單時,也不會一次一口氣將訂單調整到位,而是分次部分調整,以此來謹慎確認趨勢
Delivery Delay 
(交付延遲)
從工廠收到訂單到實際生產交付給經銷商的時間。這也是整個系統中,最重要的 delay,因為這種 delay 是你無法直接控制


  1. Delays 對於 feedback loops 的影響:
Feedback loops
Influence 
Balancing loops
Delays 會造成系統震盪,讓 balancing loops 更難維持系統穩定
Reinforcing loops
Delays 會無法做有效干預,難以建立起有效、循環的強化流程


  1. 不幸的是,問題分析專家常會忽略 delays 及其造成的負面影響。當 delays 造成的傷害變得顯而易見時,人們第一個反應一定是趕快把火熄滅。但是這種干預方式通常沒有經過審慎思考或良好測試,常常會有過猶不及的狀況發生。
  2. 改變系統的延遲可以讓系統更易於或更難以管理。system thinkers 非常重視延遲這個主體。我們必須總是對於系統產生的延遲保持警戒,關注會延遲多久,並注意延遲是因為資訊流或實際流程所造成。若你不知道系統的延遲發生在哪裡 (where)、會延遲多久 (how long),你就無法了解系統的動態行為。無論是拉長或是縮短系統延遲,都會對系統行為產生重大改變
  3. Systems thinking vocabulary 此章節你可以學到
  4. 21 世紀的我們,總是想要快速解決問題,但是效果總是不顯著。原因在於我們只針對問題表徵治療,並沒有找出根本原因。有些干預措施,即便立意良善,可能會產生未預期的後果,反而適得其反。
  5. 系統了解的層級
level
description
事件
(Event)
  • 包含每日發生的事件的分解,包含工作、讀書、喝咖啡等
  • 有些事件需要立即的反應。例如當夏天水患發生時,要及時安置災民、協助災民修繕受損房屋等,這些立即反應是合理的,但是從系統觀點來看,這是不足夠的。當颱風再次來襲,你還是無法阻止水患發生,你只有針對事情表徵解決問題,沒有解決水患的系統基本結構
事件發生模式
(Patterns of Events)
  • 是一系列的事件,找出其重複發生的模式
  • 政府當局可以針對過去五年、十年、五十年、甚至一百年的水患資料進行分析,例如夏天發生颱風機率高,若適逢滿潮,淹水機率更高。
  • 了解系統發生模式可以協助我們預測事件發生的機率,但是對於避免水患並沒有提出實質的解決方案
系統性結構
(Systematic Structure)
  • 事件發生的系統性結構,事件的發生是因為某種結構所造成,是事件產生器 (event generator)
  • 如果專家能夠提出好問題的話,才能有效解決水患問題。例如堤防是否需要建更高?提防的結構是否穩固?政府的下水道等基礎建設是否有同步改善中?若相關行動有同步開始的話,才能真的改善水患現況,並建立更好的未來。
共同願景
(Shared Vision)
  • 共同願景會產生系統結構,可將其想成系統結構產生者 (systemic structure generator)
  • 共同願景是眾人集體的心智模型、信念與習慣所組成。災民由於恐懼水患等因素,要求當水患發生時,政府應該要立即予以協助,這種思維應轉換成政府應盡最大努力避免水患發生。
  • 當水災獲得控制後,就會慢慢的一層一層往上思考,會思考未來,而非只看當下,會找出根本原因解決問題,而非頭痛醫頭,腳痛醫腳


  1. 不同的階段會問不同的問題
level
description
事件
(Event)
  • 如何以最快速的方式,解決眼前的問題?
  • 強調 reactive
事件發生模式
(Patterns of Events)
  • 事件不斷重複發生,這之間有沒有什麼趨勢或發生模式?
  • 強調 adaptive
系統性結構
(Systematic Structure)
  • 有沒有什麼心智模型或組織結構,導致這樣的事件發生模式不斷重複發生?
  • 強調 creative
共同願景
(Shared Vision)
  • 有沒有什麼成文或不成文的願景,產生這的系統性結構?
  • 強調 generative


  1. 將 system story 講述清楚,是了解系統發生什麼事情的第一步。以書中所舉的例子,Acme 公司敘述一間面臨不斷下滑地銷售量且苦苦掙扎的企業故事;作者祖父的村落,討論著有關如何干預水患的觀點轉換的故事。當故事了解清楚後,就要將故事內容以圖形的形式 (form of a diagram),繪製出來。在 systems literature 中,這些圖形就是所謂的系統思考工具 (systems thinking tools),包含 brainstorming tools、dynamic thinking tools、structural thinking tools 及 computer-based tools。
  2. Brainstorming tools
Diagrams
Description
The Double-Q (QQ) Diagram
  • QQ 是定量 (qualitative) 與定性 (quantitative) 的縮寫
  • 圖形結構是基於因果關係圖 (case-and-effect diagram),又被稱為魚骨圖
  • 魚骨圖通常搭配腦力激盪法(Brain Storming)進行。首先在魚頭紀錄或寫下待解決之問題(例如不良率增加是一個問題),或是某一表徵觀測現象(例如公司營收下降)。之後,召開會議,並透過團體腦力激盪寫下大家認為造成此結果之主要原因。實際操作上,會議主持人可要求與會成員,每人先憑空想像3個原因,之後再經由小組成員系統化的分類這些原因。將相關原因歸納後之主原因即是大骨、子原因即是中骨、孫因素即是小骨


  1. 魚骨圖分析共分為四步驟:
  2. 如果拆解一條魚的結構,可以發現每條魚都有骨頭與刺,因此我們可以將魚骨圖中的分析架構分為大骨頭、中骨頭與小骨頭,它們對應到特性要因圖的文字,就是所謂的一次因、二次因、三次因,表示原因有大有小,會透過層層分析找出問題的關鍵所在。通常一個完整的魚骨圖會從幾個構面進行分析,分別為:人員、機械、材料、工法、環境等,對應到活動的舉辦來說就是所謂的人員(活動參與對象、利益關係人)、機械(活動使用哪些器材、道具)、材料(活動的內容規劃特色為何)、工法(活動執行的流程是否順暢)、環境(活動的場地安排注意事項)等,透過每個構面進行細部拆解,最後找出關鍵點進行確認與修正。
  3. Dynamic Thinking tools
Diagrams
description
Behavior-Over-Time (BOT) Diagrams
  • BOT Diagrams 用來捕捉系統行為的動態性,用來展現變數間的關係。
  • 以 Acme 為例,A 代表對於產品的創新改善數量、B 代表原本的銷售量、C 代表實際銷售量。我們可以從圖中清楚看到,儘管創新量增加,銷售卻下滑,系統中一定哪裡有出錯了。
Causal Loop Diagrams (CLDs)
  • CLD 對於系統結構有更深入的了解,其還捕捉了變數間的互動動態性。
  • BOT 與 CLD 可以一起運用,BOT 抓出有哪些變數,CLD 提供變數間 feedback 的特性,reinforcing feedbacks 或 balancing feedbacks。
Systems Archetype Diagram
  • 此圖形顯示在不同案例中,重複出現的動態性 (reoccurring dynamics)。archetypes 是多個 balancing 與 reinforcing loops 的綜合,用來簡化真實世界的事件。我們所講述的故事,利用這些 loops 提供我們對系統更深層的了解。


  1. Structural thinking tools
Diagrams
description
Graphical function diagrams
  • 用來顯示兩個變數間、非線性的關係。通常用來顯示難以衡量的無形變數,如士氣、心理壓力等級等。
Structural-behavior pairs
  • 此種圖形用來連結一個給定的結構與適當的行為。此動態結構可以用來發展電腦模型,如指數型增長、延遲、來回震盪等。
Policy structure diagrams
  • 這些複雜的圖形用來顯示推動政策的流程,聚焦於影響決策流程的重要變數,並為了未來解決方案,建立有用的樣本系統結構。


  1. Computer-based tool 包含電腦模型、飛行管理模擬等,需要高度的技術技巧才能建立,但是建立完備後,需要很少的進階訓練就能使用、上手。
  2. 五個基本的 system archetypes
System Archetypes
Description
Fixes that backfire
(短視近利)
  • 某個解決方法,在短期可以收到好效果,但是對於長期來說,反而會是個適得其反的壞方法。
  • 例如,當你胃痛的時候就吃胃藥,可以看到立竿見影的效果;但是,此舉會延誤你去看醫生的時機,當胃藥已無法解決你的胃痛才去看醫生時,可能已經為時已晚。
Shifting the burden
(頭痛醫頭、腳痛醫腳)
  • 沒有找到根本原因,徹底解決問題,而是將問題塞到另外一個地方,導致問題跑來跑去,你反而需要花費更多的時間、心力來控制這個問題。
  • 例如,頭痛醫頭,腳痛醫腳,沒有找出病因,對症下藥。
Limits to success
(成長限制)
  • 為了繼續成長,每年都要花費更大的資源與力氣來維持成長,最終都會因為無法跟上成長所需的資源與力氣而中斷。
  • 成長是有限制的,你應該提前做好準備,找出可能會阻礙成長的絆腳石與威脅,在其真正造成危害前,盡早解決
Tragedy of the commons
(公共資源的悲劇)
  • 公共資源是大家所共同擁有且是有限的,若過度使用,反而會造成資源用罄,大家都沒得使用。例如,美麗的國家公園會吸引觀光客,替國家公園帶來收入,但是若未做人數限制,人潮將會摧毀國家公園的天然美景
  • 可透過規範公共資源的使用方式,來約束公共資源的使用,例如道路資源是大家共用的,透過紅綠燈來決定誰先行。
Accidental Adversaries
(意外的競爭者)
  • 其中一方因為無意的行為,損害另外一方的利益,導致彼此變成競爭對手。


  1. 其他四個 system archetypes
System Archetypes
Description
Success to the successful
(贏者全拿)
  • 贏者從中獲得系統性報酬,由於 reinforcing feedback loop 的強化,讓贏者在未來更容易獲勝,這就陷入贏者全拿的系統陷阱。
  • 例如,奧運金牌選手,可以獲得更多的贊助、裝備、營養補給與教練,使其未來的競賽更容易成功
Escalation
(競爭升級)
  • 當兩個或兩個以上的競爭者,不斷地要贏過對方,競爭程度會越來越強烈,直到其中一方倒下。例如,冷戰時期,美俄的軍備競賽。
  • 你應找出競爭升級的結構 (escalation structure),找出停止繼續競爭升級的方法,或於第一時間就阻止
Drifting goals or goal erosion
(目標侵蝕)
  • 這在組織相當常見,倘若當初設定的目標無法達成時,就開始降低目標的標準。
  • 將績效標準設定為絕對的標準,甚至設定更高的標準來強化最佳的績效標準,避免受到壞影響,因而降低標準
Growth and underinvestment
(忽視潛在成長機會)
  • 若需求超過供給,績效會受到影響,最終會導致需求降低。若趨勢持續,最終會因為認為需求降低,導致不予投資。
  • 潛在的成長永遠無法達成,因為沒有足夠的證據證明,就不做投資。決策者的投資計畫應該有清楚的觀點,而非仰賴舊資料與舊趨勢


  1. system archetypes 並非獨立存在,彼此間是有關係的
  2. 追求成長與解決問題可能產生的系統陷阱:
  3. Fixes that backfire (短視近利) 的注意事項

注意事項
說明
① 找出問題的徵狀,但是不要立即將其當成解決問題的目標
  • 當我們面臨危機時,很容易有衝動想要馬上解決所發現的問題癥狀。這代表我們將精力與資源用來處理問題的徵狀 (symptom treatments),而處理非問題的根本原因 (root cause)。
  • 當遇到問題時,一開始一定會進行搜集與定義問題的徵狀,不要在此階段尋找解決方案,因為你還沒接觸到真實根本原因
② 有哪些未預期的結果?
  • 我們所採取的行動都會產生結果,但是很多都是未預期的。
  • 透過成因圖 (casual diagrams),你可以找出你所採取的矯正行動,產生哪些可能的副作用 (side effects)
③ 有些 loops 會產生問題的徵狀
  • 當我們要全力解決問題前,應該先後退一步,你反倒是應該先去尋找造成此問題長久存在的因素,再去尋找問題的解決方案
  • 以 Acme 銷售下滑的問題為例,銷售下滑是問題癥狀,但是根本原因是產品單位推出新產品,電腦化基礎建設不足,導致交貨延遲、客戶抱怨、商譽受損、銷售下滑。產品單位為了提升銷售業績,又推出新產品,導致惡性循環。quick fixes 與 root causes 常常以 reinforcing 關係相連結
④ 尋找高槓桿干預點
  • 高槓桿干預 (high-level interventions) 是要改變結構層級 (structural level),例如修改規則與政策,最終影響決策方向。以我們的 loop diagrams 來說,高槓桿干預代表增加或減少某些連結
  • 每次干預與改變都會產生效果,但也會帶來潛在的、未預期的副作用。當我們想做出改變前,應事先找出潛在的威脅與未預期的後果
  • 預算超支與時程延遲,是兩件常見的問題。問題常起於外部壓力的結果,例如削減成本、符合品質標準等壓力。我們常會用一些解決方案,例如不管用任何手段,要準時交付。但是這種解決方案常會產生未預期的後果,可能是讓原本的問題更嚴重 (例如預算超支更多),或是產生相關問題 (例如符合期中時程,卻在最後時程失控)。

2020/03/10

[Java] 如何依序產生英文字母

Requirement
如何指定開始與結束的英文字母?如起訖為 P 與 AC,程式自動產生 P, Q, R, ......AB, AC

How-To
英文字母產生邏輯

Sample code:
package com.test.tool;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@SpringBootTest
@Slf4j
public class TempTest {
    @Test
    public void doTest() {
        List<String> alphabets
                = Arrays.asList("A", "B", "C", "D", "E", "F",
                                "G", "H", "I", "J", "K", "L",
                                "M", "N", "O", "P", "Q", "R",
                                "S", "T", "U", "V", "W", "X",
                                "Y", "Z");

        List<String> resultList = new ArrayList<>();

        String startStr = "P";
        String endStr = "AC";

        int start = alphabets.indexOf(startStr) + 1;
        log.debug("start = {}", start);

        String result = "";
        while(!result.equals(endStr)){
            result = getAlphabet(start);
            resultList.add(result);

            start++;
        }
        log.debug("resultList = {}", resultList);
    }

    public String getAlphabet(int number) {
        StringBuilder columnName = new StringBuilder();
        while (number > 0) {
            // find remainder
            int remainder = number % 26;
            // if remainder is 0, then a 'Z' must be there in output
            if (remainder == 0) {
                columnName.append("Z");
                number = (number / 26) - 1;
            } else { // if remainder is non-zero
                columnName.append((char) ((remainder - 1) + 'A'));
                number = number / 26;
            }
        }
        // Reverse the string and print result
        return columnName.reverse().toString();
    }

}

console:
09:57:56.458 [main] DEBUG com.cht.tool.filegenerator.TempTest - start = 16
09:57:56.475 [main] DEBUG com.cht.tool.filegenerator.TempTest - resultList = [P, Q, R, S, T, U, V, W, X, Y, Z, AA, AB, AC]



2020/03/09

[Java] [Spring] [JDBC] [Oracle] How to call stored procedure - example 2

Assume I have a stored procedure as bellows:
create or replace PACKAGE PACKAGE1 AS 
     PROCEDURE TEST_EMPLOYEE 
    (
      I_GENDER IN VARCHAR2 DEFAULT 'M',
      O_RESULT OUT SYS_REFCURSOR,
      o_return_code OUT INT
    );
END PACKAGE1;

create or replace PACKAGE BODY PACKAGE1
AS
   PROCEDURE TEST_EMPLOYEE 
    (
      I_GENDER IN VARCHAR2 DEFAULT 'M',
      O_RESULT OUT SYS_REFCURSOR,
      o_return_code OUT INT 
    ) AS 
    BEGIN
      OPEN O_RESULT FOR 
      SELECT ID, NAME, PHONE, ADDRESS, GENDER
      FROM EMPLOYEE 
      WHERE GENDER = I_GENDER;
      o_return_code := 0;
    END TEST_EMPLOYEE;
END PACKAGE1;


Step1. Create a custom repository interface:
package com.example.jpa.repository.custom;

import java.util.Map;

public interface EmployeeRepositoryCustom {

    Map<String, Object> findEmployee(String gender);

}


Step2. Create a custom implementation class:
package com.example.jpa.repository.impl;

import com.example.jpa.entity.Employee;
import com.example.jpa.repository.custom.EmployeeRepositoryCustom;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;

import java.util.Map;

@Slf4j
public class EmployeeRepositoryImpl implements EmployeeRepositoryCustom {

    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;

    @Override
    public Map<String, Object> findEmployee(String gender) {
        SimpleJdbcCall jdbcCall
                = new SimpleJdbcCall(jdbcTemplate.getJdbcTemplate().getDataSource())
                .withCatalogName("PACKAGE1")
                .withProcedureName("TEST_EMPLOYEE")
                .returningResultSet("O_RESULT", 
                                    BeanPropertyRowMapper.newInstance(Employee.class));

        SqlParameterSource input = new MapSqlParameterSource().addValue("I_GENDER", gender);

        Map<String, Object> output = jdbcCall.execute(input);
        output.entrySet().forEach(entry -> 
            log.debug("key = {}, value = {}", entry.getKey(), entry.getValue()));

        return output;
    }
}



Step3. Create an repository interface
package com.example.jpa.repository;

import com.example.jpa.entity.Employee;
import com.example.jpa.repository.custom.EmployeeRepositoryCustom;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends CrudRepository<Employee, String>, EmployeeRepositoryCustom {
}


Step4. Create a test case:

package com.example.jpa;

import com.example.jpa.entity.Employee;
import com.example.jpa.repository.EmployeeRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

@SpringBootTest
@Slf4j
public class TestEmployeeRepository {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void testFindEmployee() {
        Map<String, Object> output = employeeRepository.findEmployee("F");

        List<Employee> employees = (List<Employee>) output.get("O_RESULT");
        for (int i = 0; i < employees.size(); i++) {
            log.debug("employee = {}", ToStringBuilder.reflectionToString(employees.get(i)));
        }

        int returnCode = ((BigDecimal) output.get("O_RETURN_CODE")).intValue();
        log.debug("returnCode = {}", returnCode);
    }

}


Step5. Check result:
[           main] c.e.j.r.impl.EmployeeRepositoryImpl      : key = O_RESULT, value = [com.example.jpa.entity.Employee@2adbd899, com.example.jpa.entity.Employee@d6831df]
[           main] c.e.j.r.impl.EmployeeRepositoryImpl      : key = O_RETURN_CODE, value = 0
[           main] com.example.jpa.TestEmployeeRepository   : employee = com.example.jpa.entity.Employee@77ccded4[id=2,name=Mandy,phone=0911111111,address=Taipei,gender=F]
[           main] com.example.jpa.TestEmployeeRepository   : employee = com.example.jpa.entity.Employee@2bb717d7[id=3,name=Gina,phone=0911111111,address=ChiaYi,gender=F]
[           main] com.example.jpa.TestEmployeeRepository   : returnCode = 0