跳到主要內容

發表文章

有時你得靠問問題來回答問題

Photo by rawpixel.com from Pexels 技術顧問看似應該要回答客戶各種技術問題,但我發現問對問題才能幫助你更好的回答問題。 客戶未提供必要資訊 某天去了一家從未去過的麵店,因為趕時間看一眼菜單就點「 乾麵加蛋 」,付款時發現店員可能聽成「 麵加蛋 」,因此做成「 外帶、肉羹麵加蛋 」,跟我預期的完全不同。我的確是忘了說我要「內用」,店員如果只聽到我點「麵」,而店裡提供兩種麵,要避免做錯,他應該要問我: 肉羹麵還是乾麵? 外帶還是內用? 其實客戶未提供必要資訊是可以理解的,因為面對不了解的領域,無法分辨哪些資訊是必要提供的。另外,有些客戶也會基於一種「你應該要能解決我問題」的心態,希望直接把問題丟給你解決。 提問是種專業 技術支援上經常遇到這種情形,客戶通常不會提供他所遇到的問題的完整或必要的資訊,甚至只會說「頁面不會動」這種很籠統的描述。因此我們要根據每個不同案件,來提出必要的問題。有人也許會擔心問客戶問題會不會讓人覺得不專業,好像我們應該要能直接吿知答案。但冷靜想想,不管是誰都無法在缺乏必要資訊的情況下找出問題所在或提供精準的服務。其實遇到問題的客戶是擁有完整資訊、最接近問題的人,畢竟問題就發生在他眼前,只是他不知道哪些資訊能解決問題。而我們提問代表著我們對問題狀況的熟悉與專業,知道需要哪些資訊來解決問題,因此良好的提問能夠有以下效果: 引出關鍵資訊 釐清描述不清的現象 確認彼此認知是否一致 因此能夠問對問題也是一種專業。

把事做好,不能只靠能力

  今年重新整修廚房,遇到了三個問題,都是櫥櫃安裝師傅的錯誤態度造成的。 犯錯不承認 在安裝水槽、水龍頭時,需要先關閉家中水塔的供水,這位師傅關了一次沒成功,又再去關第二次,待他安裝完離開後,我們發現家中水壓變得異常的小。我初步判斷跟師傅關閉水塔管路非常相關,因為在這之前都很正常,但是我們向師傅反應時,他卻說他只有開關管路而已,並沒動到其他部分,並說我們家水壓本來就比較小。言下之意是說跟他沒關係,不是他造成的。但是當天晚上我們反覆測試與確認,認為還是跟開關水塔管路有關。隔天還是知會設計公司派水電師傅來檢查,後來的確發現是櫥櫃師傅關錯水管線路造成。 不懂裝懂 到了安裝洗碗機門片時,櫥櫃師傅告知我們洗碗機額外附上的鐵片是避震用,但沒想到洗碗機原廠師傅來時,卻說那個額外的鐵片是用來安裝門片用的,因此櫥櫃師傅安裝方法不對。這令我們有點生氣,他完全不懂卻說得那麼篤定。 未事先準備 而安裝 IH 爐時,我們聽到他跟同事交談說是第一次安裝。早上裝完後,下午設計公司又派人來重裝,並解釋說原本用接插頭的方式,易造成過熱而插頭熔掉,改成電線連接比較安全。我們都心想還好剛剛只有稍微測試,還好沒真的開始用。 態度才是重點 每個人都有其能力限制,清楚的認識自己能做什麼、不能做什麼,比一昧相信自己都能辦到更好。這位安裝師傅在面對自己不擅長的項目時,如果能事先做更多準備,事後能以謹慎態度面對,我想我們的觀感也不會這麼差。而且看起來他也沒有從第一次犯錯中學習,後面兩次犯錯在我看來都是同樣的態度造成。雖然最終都沒造成什麼嚴重的後果,但誰能保證下次也能這麼幸運呢?

我的孩子不想努力,我怎麼從旁給力

先前發現老家有個被棄置的蛇板,覺得可惜就拿回來玩。但我覺得很難玩,連站都站不上去。但小女兒卻是興致盎然,很開心的一直試著滑,也邀請要好的鄰居女孩一起玩。我也覺得蛇板應該非常符合她的興趣。但是一兩週後的某天,突然發現她不玩了,我感覺應該不是玩膩了,因爲才剛拿到沒多久。而且不管是鄰居女孩或我找她一起玩,她就是不願意,問她原因也不說,只說就是不想玩了。 以我對她的了解,她應該會很喜歡蛇板,而且她剛開始看到蛇板明明很興奮,為何後來突然就不想玩了呢?我覺得小女兒很擅長戶外運動,我也希望能多多培養她這個專長,所以她不玩蛇板我覺得很可惜。但我也不可能威脅說:「去給我溜蛇板,不然我就要丟掉它」。因此我選擇接受她這個狀態,也不一直追問她原因。 我先嘗試以身作則的方法,就是每次跟小女兒在門口玩時,我就會花點時間練蛇板,也會上網看一些基本教學,慢慢從能先站上板子滑一小段,到漸漸可以滑行較長距離,後來更熟了之後也學會轉彎及加速。過程大概花了一兩週,小女兒也都在一旁看著我滑。我心裡希望小女兒因著看著我學習、進步的過程,能夠激起她想要學的慾望。在我溜得蠻順了之後,我就試探性的鼓勵她說:「你看很簡單喔!多練習幾次就可以了」、「你看這不難喔,我本來也是不會,現在也會滑了」。你覺得這樣她就願意玩蛇板了嗎?沒有! 就這樣又過了一陣子,我跟老婆討論到這件事時,她就提醒我,小女兒幼稚園時期,也是一時興起堅決要學直排輪,沒想在第二堂課摔了個大跤之後,就再也不學,好說歹說都沒用,每次上課都只是把直排輪鞋帶去,坐在場邊看其他孩子溜,再也沒上過場。從這個事件我突然意識到,小女兒可能是怕摔。於是我就嘗試性的問小女兒:「爸爸幫你買護具好不好?」,她很開心地答應了。穿上護具後,果然她就願意再次開始玩蛇板,甚至會假摔來試試護具的防護力。過沒多久,就很順利地學會滑蛇板,而且熟練之後,就因為嫌熱把整身護具脫掉。後來蛇板就變成她的戶外遊戲之一,不用我叫也會自己去玩。 從這件事我得到幾個經驗: 🍀  要接納孩子的放棄與失敗。 當小女兒突然不玩蛇板時,我沒有一直鞭策她或逼問她原因,就先接納她放棄的決定。沒有因為這個放棄就對她施加更多壓力,或是對她失望。 🍀  父母不能總是從自身經驗來幫助孩子。 我以為展示我自己的成功學習蛇板經驗可以誘發小女兒的學習慾望,那是從我自己是愛好學習的人的角度出發的,這種...

jQuery event.namespace

最近才發現 jQuery event.namespace 可以讓你只移除自己註冊的 event listener 而不移除其他人註冊的。 往常我們多半指定 event type 來註冊: $ ( document ) . bind ( 'mouseup' , myListener ) ; 並指定同一個 event type 來移除: $ ( document ) . unbind ( 'mouseup' ) ; 但有時候你使用別人的 widget 或 library 也許它也註冊了同樣事件的 listener,或是你不確定有沒有,為了避免不小心 unbind 其他人的 listener,我可以在註冊加上 namespace 如: $ ( document ) . bind ( 'mouseup.line' , myListener ) ; .line 就是自訂的命名空間。 然後可以移除該 namespace 的所有 listener: $ ( document ) . unbind ( '.line' ) ; 這時如果 document 上也有別人註冊的 mouseup 就不會被你呼叫的 unbind() 移除。

margin collapse 造成垂直捲動條

有次我發現即使我將 html, body 都設定沒有 margin 如: html { height : 100% ; margin : 0 px ; } body { height : 100% ; margin : 0 px ; } 但頁面仍然產生垂直捲動條(scrollbar),檢查後發現 body 跟 text1 的上白邊合併,造成 body 向下移動,因為 body、html 高度相同,下移就造成右方垂直捲動條出現。 另一個例子:text1 的下白邊與 text2 的上白邊合併 (collapse) 這頁範例 可以重現以上問題。 這不是 bug,而是規格上定義的 margin collapse : 8 Box model / 8.3.1 Collapsing margins 解法  < h1 style =" display : inline-block " > text 1 </ h1 > < h1 style =" margin-top : 0 " > text 1 </ h1 >

ZK 與響應式網頁設計(RWD)

響應式網頁設計 (RWD) 就現今而言應該是許多網站必備的條件之一,ZK 框架可以幫你從一個抽象層次較高的角度滿足 RWD,不需要了解 CSS Media Query 的細節。 元件層級 元件層級的 RWD 是由元件內部自動完成的。每個 ZK 元件都能區分瀏覽器的種類,因此會自動依據桌上型或行動裝置瀏覽器來自動調整元件的外觀,不需開發者介入,例如 datebox 在桌面瀏覽器會跳出小月曆: 在手機瀏覽器就會自動變成下方滾動條: 頁面層級 頁面層級的 RWD 因為牽涉到版面設計,因此無法靠元件自動完成,因為畢竟每個頁面需要的版面設計皆不相同,這通常不是一個技術性的決定,而是政策性或設計性上的決定,因此 ZK 提供一些工具讓你實現 RWD。以下根據你想要達成的效果,概述你可用的工具。 寬度、版面自動調整 如果你想要做出的效果是,畫面內各區域自動調整寬度,較窄時調整位置,我非常推薦搭配 Bootstrap Grid 與 ZK 一同使用,因為 易於整合 。使用 Grid system 只要在 div 套上 CSS class,如 col-md-4 ,因此跟 ZK 整合很容易,不需要特別的設定與程式。 元件變換 如果你希望畫面寬、窄時,各用不同的元件來顯示,例如側邊欄在畫面變窄時消失並換成小的選單圖示,這時元件本身當然無法得知要做這樣的轉換,你可以透過 onClientInfo 事件 或 @MatchMedia 來偵測畫面寬度變化,並針對該變化調整畫面元件。 更多細節可以參考以下資源: Use Bootstrap with ZK: Responsive Admin Template 範例專案 線上展示

innerHTML 的安全風險

問題 若你要實作一個使用者輸入的功能,並把輸入的內容顯示在網頁上時,應避免把使用者輸入的值直接指定給某個 element 的 innerHTML 。因為若是使用者輸入包含 HTML、JavaScript, innerHTML 就會解析並執行: var userInput = '<img src="javascript:alert("XSS")">' element . innerHTML = userInput ; 因此可能 user A 的惡意輸入,會被系統顯示到 user B 的畫面上,進而執行特定成程式碼而造成 XSS 攻擊 。 解法 一般會先想到 encode HTML,但是如果只是要顯示文字,將使用者輸入值指定給 textContent 會更簡單,無需 encode,該值會被當成純文字處理,並不會執行 javascript。 var userInput = '<img src="javascript:alert("XSS")">' element . textContent = userInput ;

Mac 藍芽無法使用問題 (not available)

最近開始用藍芽,發現 Mac 一旦休眠之後藍芽就會不能使用,變成關閉狀態: 或是顯示 Bluetooth Not Available 即使到設定畫面, 啟動藍芽(turn bluetooth on) 的按鈕也是 disabled 無法點。 必須要 重開機 網路上有提到 許多解法 ,但比較少提到的是如果你曾安裝 Android File Transfer 就會有此問題,解決方法只有移除一途,我就遇到這個情形,已驗證過,的確可以解決。 這篇文章有提到 。

避免落入預期解法的死胡同

買錯票 某天我要去台北,卻發現自己前一天起迄站買反,買成 台北 —> 台南 ,但是搭車前才發現,詢問服務台人員,只得到回覆說發車前 30 分之前才能退票,自己也依稀記得規定是如此,因此只好認栽,誰叫自己買票沒看清楚,趕緊自己去重買一張到台北的車票。後來老婆提醒我也許可以換票,再至服務台詢問,服務小姐說若是錯過班車,可以當天的票搭乘較晚班次的自由座,但是我給她看我的票是早上出發,卻要搭晚上的車,我就解釋是因為我買錯票,她猶豫了一下,竟然說了一個聽起來可以幫我換票的說詞(我忘了他確切的用詞),我雖然感到驚訝但是我已經買了到台北的票,只能說來不及了,她就建議我仍然採用換搭自由座的方法。 預期解法 - 退票 這件事我也犯了一個跟某些客戶常犯的錯誤,就是「沒有提供背景資訊」,或說「 預期解法 」。我心理預期我的問題的解法只有「 退票 」,因此我只問了特定的「退票」事宜,發現不能用「退票」解決,就以為沒有解法了。如果我能第一時間提供比較完整的背景資訊(也就是買錯票的狀況)給服務台,因為服務小姐擁有較完整的解決方案,她可以從我的背景資訊中,更清楚我的需求,進而幫我想到其他解法。 破除方法 - 主動探詢 當然服務是雙方的,如果服務台小姐可以在我第一次詢問時,就更主動探詢更多的背景資訊,她也就有機會促使我講出原本的問題,並從中找出其他解法,最終就提供了一個更好的服務體驗。 回到原始問題 在 ZK 技術支援上,有時客戶問到一個特定的功能可否做到某個效果,問題很簡短,也沒有交代需求或應用情境。如果該功能確實可以達到該效果,我通常會簡短肯定回覆,只是確認或補充說明。如果無法做到,或是該功能跟客戶預期的效果沒有什麼關聯,我會在信末提說:「 如果你能告訴我們你的應用情境,也許我們能找到其他解法 」。因為我推測這位客戶已經落入「預期解法」的思維,他已經認定這個特定的功能就是解法,如果不能做到他的要求,就是不能解。我問的那句話就會 促使客戶描述原本的應用情境 ,我可以據此去找到另外的替代方案或更好的解法。

[Tomcat] 自動部署

問題 如果 WAR 檔很大,部署的時候,解壓縮 WAR 如果需要 30 秒,這段時間應用程式就會停止服務。 解法 減少部署過程中 ,應用程式無法服務的時間。 undeployOldVersions https://tomcat.apache.org/tomcat-8.0-doc/config/host.html war naming http://tomcat.apache.org/tomcat-7.0-doc/config/context.html#Naming 綜合以上兩種設定,我們可以在已經部署 myapp.war 的情況下,再部署新版 myapp##2.war 待新版部署完畢,Tomcat 就會自動移掉舊版。

Chrome 放棄請求

有人遇到 Chrome 放棄 (abort) ajax 請求 (request) 的狀況,但原因不明。只知道 XMLHttpRequestEventTarget.onabort 被呼叫了。 有人查了 Chrome 的程式碼與也有人歸納自己的經驗,發現有以下幾種情形 Chrome 會放棄請求: 發出請求的 DOM 元素已經被刪叫 例如說 <img> 將要載入圖片,但在載入發生之前, <img> 就被刪掉了。 做了某件事造成不需要該請求 例如載入一個 iframe,但卻改變 src 屬性,因此原本要載入的內容就不需要了。 多個請求中的第一個失敗了 架設有多個請求陸續發到同一個伺服器,先發的某個請求因為網路問題失敗了,例如 DNS 錯誤、400 等,那後續的請求就會被取消 Self-signed certificate 不被 android Chrome 信任 你發出一個請求到一個 self-signed 網站,因為 SSL certificate 不被 Chrome 信任,因此它就悄悄地取消該請求,也沒有任何訊息。要下載與安裝該 certificate 到 Chrome 中才能解決。 點擊在 form 中的 <button> 若點擊在 form 中的 <button> 要發出 AJAX 請求,也會被瀏覽器取消。 你可以改用 div 或 span 。如果必須要用 button ,必須呼叫 preventDefault() 。 ajax 請求違反 same origin policy 參考資料: https://stackoverflow.com/questions/12009423/what-does-status-canceled-for-a-resource-mean-in-chrome-developer-tools/13459106#13459106 http://twincreations.co.uk/ajax-request-canceled-status-with-no-http-header/

[Maven] 增加建構 (build) 過程的資訊到 Manifest 中

自動產生建構相關的資訊 在部署到伺服器後,有時會弄不清楚這個到底是根據哪一個 commit 發布的、何時發布的、某個函示庫使用哪個版本,或是 不想一直修改版號,想要用發布時間代替版號 頻繁的部署到伺服器,甚至部署到不同的伺服器,你想知道目前這個伺服器上目前到底是哪一版、或執行哪個 commit 的 code 這時你可以把建構過程的資訊加入 manifest 中,再由程式讀出來顯示在適合的地方。 用 buildnumber 產生 git commit, git 分支 (branch) 等資訊 < plugin > < groupId > org.codehaus.mojo </ groupId > < artifactId > buildnumber-maven-plugin </ artifactId > < version > 1.4 </ version > < executions > < execution > < goals > < goal > create </ goal > </ goals > </ execution > </ executions > < configuration > < revisionOnScmFailure > 0 </ revisionOnScmFailure > < useLastCommittedRevision > true </ useLastCommittedRevision > </ configuration > </ plugin > 用 build-helper 產生台灣時間 (GMT+8)。因為 project.build.timestamp 是 UTC+0 時間。 < plugin > < groupId > o...

testcafe 除錯

某次用 testcafe 測試時,呼叫某個 library 的函數後,整個瀏覽器就沒反應,也無錯誤訊息,完全無法得知發生何事。 這時只好追蹤進該函數才能知道發生何事,以下兩個參數可以啟用 node.js server 端的除錯器: testcafe --inspect --debug-brk chrome ./test.ts 參考 All the tricks that help you debug TestCafe tests 只要在程式碼中寫下 debugger; 該除錯器就會幫你停在那ㄧ行,接下來你就可單步執行去找問題。 最後發現是 Promise 中的 lambda function 內有錯誤: fs.copyFileSync 是 undefined,因為從 node 8.5.0 之後才存在該函數。 後來發現這兩個參數其實是 node.js 的除錯參數,看來 testcafe 本身也是執行 node.js。

2018 台灣領袖高峰會心得 - 面對失敗

幾乎每個講者都提到失敗。 對個人來說,不要害怕失敗,要勇敢面對,甚至要把失敗當做養分,當作天然資源一樣運用,從中我們可以學習、並改進,進而成長。不要把失敗當做是對自己的否定,那只是你人生中必然會出現的一種狀況。 一個例子 我想到前陣子一個發生在我自己身上的事。 某次我在試用公司產品時,發現了一個缺陷,回報給產品團隊時,他們卻說他們那邊無法重現這個問題,但我這邊卻能很明確用幾個步驟產生該缺陷。我與同事花了不少時間找原因,比較我跟產品團隊的環境差別,後來才突然發現我電腦上的客戶端程式與產品伺服器版本不同,因此連線上產品伺服器去操作的時候才會產生該缺陷,然而必須要使用客戶端程式與產品伺服器相同版本才能正確運作。 剛發現原因的時候,又陷入過往錯誤的思考模式,開始否定自己,覺得丟臉,感覺是自己犯了愚蠢的錯誤,弄錯版本給同事找麻煩。過了一會,冷靜一想,其實使用者操作的時候看不出伺服器版本,如果用錯誤的版本連上伺服器也不會立即產生什麼錯誤,大部分功能都可以正常使用,只是就是會在特定情況發生該缺陷。因此未來客戶也可能會跟我犯一樣的錯誤而不自覺,而這樣會導致我們跟客戶之間花費大量時間除錯。於是我建議產品團隊要增加一個版本不一致警告,當客戶端連上一個不同版本的伺服器時,會主動跳出警告顯示兩方的版本,就能避免意外使用不同版本而造成難以除錯的狀況。而主管也都同意增加此功能。

ZK 教學 - 常見錯誤用法 06 - 慎選 data binding 語法

慎選 data binding 語法 最常用的語法有以下 4 種: @init: 只從 ViewModel 載入一次 @load:載入並追蹤 ViewModel 變化 @save:寫入 ViewModel1 @bind:(雙向)等於 @load + @save 後面 3 種因為需要持續維持(追蹤)元件與 ViewModel 的綁定關係,伺服器需要建立 tracking node,因此成本較高。 依對系統的負擔排名如下: 因此如果沒有動態變化的需要,請使用 @init 。 例如在 Listbox , Grid , Tree 內的 <template> 若只是顯示資料,只需用 @init ,可以免去大量 tracking node 所需的記憶體。 例如: < grid width = " 400px " model = " @init(vm.itemList) " > < columns > < column label = " index " /> < column label = " name " /> </ columns > < template name = " model " > < row > < label value = " @init(forEachStatus.index) " /> < label value = " @init(each.name) " /> </ row > </ template > </ grid >

將 Atom 作為 markdown 編輯器

如果要將 Atom 作為 markdown 編輯器的話,本身已經內建預覽功能 (ctrl+shift+m),再加上以下兩個套件會更方便: markdown-scroll-sync 捲動原始檔案時,預覽畫面也會跟著捲動。 markdown-writer 增加熱鍵插入 markdown 語法,如 cmd+shift+’ 插入 code block。安裝完後,要先選 Packages / Markdown Writer / Configurations / Create Default Keymaps,熱鍵才會作用。

Intellij IDEA java 在 mac 下除錯模式啟動特別慢(卡住)

使用 Intellij 時,很常需要用除錯 (debug) 模式執行一個程式,但我發現在 Mac 下每次啟動都會卡住整個 Intellij 好幾秒鐘(完全無法操作),然後才會啟動,非常影響除錯效率,近來終於找到 stackoverflow 上有人提供的解法 :可以透過修改 /ect/hosts 或是啟動 File Sharing 來解決。

Inkscape 在 Mac 下只能輸入注音文問題

當我在 Mac (Sierra 10.12) 中使用 Inkscape 時,發現不管怎麼打字都只會出現注音文,如: ㄅㄨㄛ 既不能輸入英文,也不能打出中文字。我沒有完全解決,但是以下我的設定至少可以讓我輸入英文,中文輸入則必須要從其他應用程式複製後貼過來,給大家參考:

如何同時在多個 Excel 工作表 (sheet) 編輯

如果你有多個工作表 (sheet),你想要: 在每個表上的同一個儲存格中輸入一樣的內容、設定同樣的格式 在每個表上的同一個欄位上插入一行 刪除每個表上的同一個欄位 以上需求一開始你可能會想說用複製貼上,每一個表都做一次,但是這樣很麻煩也容易出錯,其實有方法可以一次作用在多個工作表上,只要: 先選擇要操作的所有工作表。可按住 Ctrl 選擇不連續的多個工作表或按住 Shift 選擇連續的多個工作表。 在某個工作表執行你要的動作如:插入行、輸入文字等 這樣該動作就會作用在所有你所選的工作表上。

Tomcat 如何自動停用(不部署)應用程式 war 檔

一般要停止一個應用程式可以透過 Tomcat GUI 管理介面停用: http://localhost:8080/manager/html 但是如果你希望啟動時就不要部署 (deploy) 某個應用程式 war 檔,就無法透過 GUI 管理介面。 例如: 我有一個應用程式部署了好幾個版本 app01.war 、 app02.war,但是我希望啟動 Tomcat 時,只要部署 app02.war ,而 app01.war 不要被自動解開部署。這時可以採用以下設定: conf/server.xml <Host deployIgnore = "app01.war" > 細節請參考: http://tomcat.apache.org/tomcat-7.0-doc/config/host.html#UserWebApplications