<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        波波Nadia

        波波Nadia 查看完整檔案

        北京編輯  |  填寫畢業院校SegmentFault 思否  |  COO 編輯 www.tvxinternet.com/u/bobonadia 編輯
        編輯

        SegmentFault COO & 思否編程聯席 CEO
        思否技術人訪談專欄作者,尋求報道、個人/廠商投稿、提供線索: nadia@sifou.com

        個人動態

        波波Nadia 贊了文章 · 3月1日

        國內首個開發者生態營銷人員現狀調研啟動,快喊你的市場小伙伴來填問卷啦!

        伴隨著信息技術的高速發展,國內一批云計算、數據庫等以開發者為核心用戶/客戶的企業快速成長,開發者生態營銷、開發者關系、技術品牌以及面向開發者這類專屬人群的產品推廣工作,正在成為新興崗位。

        我們是 SegmentFault 思否研究院,我們計劃就中國開發者生態營銷人員現狀展開調研,幫助開發者生態營銷人員更好地明確工作內容及目標,并獲得職業提升,也為正在或已經組建相關部門的企業管理者提供參考。

        注:

        • 此處「開發者」為廣義的開發人員,既包含一線工程師,也包含技術總監、CTO 等 ITPro 人群。
        • 此處「開發者生態人員」是指,您的工作目標為(或包含)面向廣義開發者人群的產品市場、品牌傳播、技術布道的相關人員,既可以是傳統 IT B2B 企業的市場營銷及運營人員,也包含技術布道師、文檔工程師、開發者關系、生態運營等崗位的人員。

        如果您是我們調研的目標人群,請立即點擊此處 https://jinshuju.net/f/VS3wFJ 開始參與我們的調研吧!我們將對您的信息完全保密,并保證不用于任何調研外的其他用途。

        image


        我們將為參與問卷調研的企業提供免費的曝光,并在調研報告白皮書的結尾處集中致謝。

        由于以上主問卷為不記名調研,如果貴公司參與了本次調研,并希望在最終調研報告白皮書中獲得?logo/公司名稱展示,請在以下表單中登記。

        另,如果您希望將貴公司在開發者生態營銷的優秀案例在白皮書中被引用分析,也可在本表單中留下您的案例描述,我們將擇優展示并在最終報告中進行案例分析(本部分內容在報告發布前會和您進行確認)。

        補充問卷鏈接:https://jinshuju.net/f/YIR2VW


        媒體 / 社區合作聯系:pr@sifou.com
        企業合作聯系:bd@sifou.com

        查看原文

        贊 1 收藏 0 評論 0

        波波Nadia 贊了文章 · 2月25日

        萬物互聯的背后,有一座“數據圍城”

        物聯網之父 Kevin Ashton 在近期的一次訪談中表示:“物聯網的真正意義,不在于收集存儲數據。更多場景下,在于正確高效地利用數據?!?/p>

        之所以提出這個觀點,是因為現階段的物聯網也被稱作數據“泛在聚合”意義上的物聯網。萬物互聯的時代造就了龐大的數據海洋,Kevin 認為應該通過對其中每個數據進行屬性的精確標識,全面實現數據的資源化。

        如果不能合理、合規、合情的利用這些數據,我們將會進入一座物聯網時代的“數據圍城”。

        一、物聯網時代的“數據圍城”

        未來學家托夫勒認為,改變世界的有四種力量:暴力、知識、金錢,以及大數據。

        戰爭改變人類社會的走向,知識影響社會的發展軌跡,金錢操縱著世界發展的命脈。而大數據之所以能位列其中,是因為“大數據”就代表著社會的形態,如何定義和理解大數據,就是如何定義和理解這個社會。

        物聯網作為一種建立在互聯網上的泛在網絡,讓互聯網時代的大數據從量變發展到了質變 —— 數據既包含數據本身,也包含了物聯網中的萬物以及物的狀態,物與物、物與人之間的交互。

        “像是一座被圍困的城堡,城外的人想沖進去,城里的人想逃出來?!边@是錢鐘書先生書中所描述的圍城,而物聯網時代的“數據圍城”,是指數據雖然可以為我們認知社會、推進社會發展提供源源不斷的動力,但卻因為我們的不“善假于物”,被不合理的分析和解讀。

        二、為何要打破這座“數據圍城”?

        打破這座“數據圍城”,既是互聯網深入發展的必然要求,也是物聯網的使命所在。

        而想要打破這座“數據圍城”,就需要在物聯網所造就的數據海洋中,構建一種“泛在的聚合”關系,使人們不再受系統環境、外部設備和數據終端的限制,便可對各類數據執行方便的檢索、計算和分析,并結合已有的數學分析模型,挖掘這些數據所代表的事務之間普遍存在的復雜聯系,從而實現人類對周邊世界認知能力的革命性飛躍。

        打破“數據圍城”的前提,是要洞悉物聯網時代的數據特點,這其中包括了數據采集、數據處理、數據共享和數據的有效性甄別四個方面,只有在特定場景中進行特定的處理,數據才能轉化成我們所需要的信息。

        以數據采集為例,作為物聯網的第一道關隘,若想打破“數據圍城”,切入點必須從傳感器入手。傳感器是物聯網感知層的數據接口,主要負責感知外界信息、響應上層指令,并配合各類終端設備完成數據的統一標準化接入。然而,不同類別的傳感器所捕獲的信息內容和信息格式均不相同,需要按一定的頻率,周期性地采集環境信息并實時更新。隨著現代物聯網的發展,傳感器本身也需要具備智能化處理的能力,即能夠對物體實施智能控制。

        因此,從傳感器入手,需要思考的是如何重新定義“傳感器”和“數據”間的關系。如果能將傳感器和智能處理相結合,從海量信息中提取、分析、加工和處理數據,輔助業務決策,適應用戶的多種需求,便能拓展出全新的應用領域和應用模式。

        牽一發而動全身,僅僅從數據采集這一層切入,便要考慮如此多的因素。因此,若想真正打破物聯網時代的“數據圍城”,將會涉及物聯網產業中的多個角色,但只要圍繞著前文提到的數據特點進行突破,一定可以達到事半功倍的效果。

        三、如何利用物聯網時代的數據特點打破“數據圍城”

        物聯網的本質是由眾多生態、系統通過特定的規律整合而成,無論生態多么龐大、系統多么復雜,其都可以細分為一個個組件和功能模塊,洞悉了這些組件和模塊的數據特點,便可以推導出與之關聯的物聯網的“破城口”。

        以現代企業智慧辦公為例,來看一下在該場景如何利用各個組件和功能模塊的數據特點打破這座“數據圍城”。通過前文的分析,已知該場景的數據特點包含三個層面:數據共享、場景化(數據處理)和效率(數據的有效性甄別)。(詳情請閱覽:《縱觀 Excel 演化史,開發者如何通過“表格技術”提升企業生產力》

        其中的數據共享,既是該場景的特點,也是物聯網時代數據的重要特征。進入物聯網時代后,辦公軟件的使用場景從 PC 和桌面端,擴展到了移動設備、智能手機、PAD 等更多的移動端,企業所面臨的智慧辦公最大的難點也已經從單純的一臺操作設備,升級成跨設備以及多人之間的協作協同,越來越多的數據需要被采集、分享和運用。

        在操作系統層面, HarmonyOS 借助了自身分布式軟總線、分布式數據管理和分布式安全三大核心能力有效解決了跨設備的協同問題。但對于具體數據信息的采集、處理、共享和多人協作編輯,仍需要各類在線文檔軟件的支持。

        在線文檔類軟件的出現,為企業辦公提供了全新的工作模式,通過將辦公數據從本地遷移到云端,打破了時間和空間的限制,用“更高的效率和更低的成本”實現了在線實時存儲和多人協作,這一點也與物聯網未來的發展不謀而合。

        可見,對于在線文檔類軟件來說,只要能貼合物聯網時代的數據特點,便可以協助打破這座“數據圍城”。而無論是數據的采集、計算分析和多人協同交互等都離不開表格控件所提供的底層支持。葡萄城,作為物聯網數據類應用落地“協作者”的代表之一,提供的正是這樣的能力。(詳情請閱覽:打破技術壁壘, 用SpreadJS 搶占“表格文檔協同編輯系統”的入市先機

        作為全球領先的軟件開發技術提供商,葡萄城以“ 賦能開發者”為使命,致力于通過各類軟件開發工具和服務,創新開發模式,提升開發效率,服務企業數智化轉型。

        葡萄城研發的純前端表格控件 SpreadJS ,提供了表格文檔協同編輯、 數據填報和類Excel報表設計的功能支持,可幫助軟件廠商和系統集成商有效應對數據處理、數據共享和數據有效性甄別等業務需要。

        純前端表格控件 SpreadJS

        借助 SpreadJS“高性能、跨平臺、與 Excel 高度兼容”的產品特性,可以讓數據處理不再受硬件、操作系統與使用環境的限制,從而實現高效的在線填報、模板設計以及多人協同,構建出更為便捷、易用、安全的分布式數據管理架構。(了解詳情:SpreadJS 純前端表格控件

        結語:物聯網時代,重新審視人與世界間的關系

        互聯網時代讓我們重塑了人與人之間的關系。而物聯網時代則將這層關系網放大,需要讓我們重新審視物與物、人與物之間的關系,這也是之所以需要打破這一座“數據圍城”的意義所在。

        了解物聯網行業的朋友都知道,物聯網產業鏈中包含八大環節:芯片提供商、傳感器供應商、無線模組(含天線)廠商、網絡運營商(含 SIM 卡商) 、平臺服務商、系統及軟件開發商、智能硬件廠商和技術服務提供商。

        其中網絡運營商負責的是物聯網的底層通道,也是目前物聯網產業鏈中最成熟的環節;芯片、傳感器等硬件廠商則是物聯網的大腦和四肢,低功耗、高可靠性的半導體芯片是物聯網幾乎所有環節都必不可少的關鍵部件之一;專供物聯網的操作系統雖然仍處于起步階段,但目前入局的都是 IT 行業的巨頭,如谷歌、微軟、蘋果、華為、阿里等。

        縱觀整個環節中,目前最容易被忽視、最需要與物聯網相結合的恰恰是物聯網應用落地真正的“協作者” —— 技術服務提供商。他們才是萬物互聯時代鏈接人與物、人與物聯網之間最直接的一根紐帶。

        打破萬物互聯時代的數據圍城,既需要迎合時代大的技術背景,也需要聚焦到每個人的需求當中。我們需要華為、阿里、谷歌這類技術先鋒為人類擴展技術的無限可能,也需要像 SpreadJS 這樣的垂直細分產品,以人為中心,在技術大潮中服務用戶的本質需求。

        segmnetfault 思否

        查看原文

        贊 26 收藏 2 評論 1

        波波Nadia 關注了用戶 · 2月23日

        SeaTable開發者版 @seatable

        SeaTable 開發者版是一款面向開發者的協同表格和低代碼平臺,可私有部署,沒有 API 調用限制,其他功能基本同云服務的免費版本一致。適合于作為企業內部的數據協作中心,實現數據的集中管理、可視化和自動化。https://seatable.cn/developer/

        關注 43

        波波Nadia 贊了文章 · 2月23日

        協同表格+低代碼,這個免費工具可作為團隊的數據管理和自動化中心

        企業中往往有很多零散的數據需要管理,這些數據往往分散在 Excel 表格、數據庫、OA 等多個系統之中。我們能不能對這些數據進行集中的收集、展示和共享協作呢?更進一步的,在這些數據之上,我們能不能快速的開發一些小應用進行數據的處理、反饋、提醒呢?

        今天我們就來分享 SeaTable 這款免費的工具,看看它如何幫我們實現零散數據的集中管理和自動化。

        先簡單介紹下 SeaTable ,它是一款新型的協同表格和低代碼平臺。它支持“文件”、“圖片”、“單選項”、“協作人”、“計算公式”等豐富的數據類型。 它幫助你用表格的形式來方便的組織和管理各類信息。它同時包含完善的 API、自動化規則、腳本運行能力,可以按照你的需要進行擴展,實現數據處理的自動化和業務流程的自動化。

        SeaTable 包含以下的版本:

        • 開發者版: 面向把 SeaTable 當做輕型數據庫使用的用戶。可以免費下載,私有部署??!沒有行數、存儲量和 API 調用的限制。
        • 云服務版: 面向有協同需求的普通用戶,有行數、存儲量和 API 調用的限制。
        • 企業版: 在以上版本的基礎上,同時有完善的權限管理和用戶管控功能,可以云端使用也可以本地部署。

        下面我們以一個多網站運維管理為例來說明 SeaTable 中數據的記錄和管理、數據可視化和自動化。

        多網站運維管理的例子

        作為開發團隊,我們往往要運維多個網站,有些給內部用,有些給外部用。我們不僅要把各種零散信息集中記錄,以方便查看和協作,還要對站點證書過期時間等,進行監控和維護。要解決這些問題,如果自己寫一個自動化工具需要花費不少時間,還不好維護。而如果用 SeaTable ,就能在很短的時間內完成,維護起來還方便。

        比如我們團隊平時管理的站點就有二十多個,全部使用的是免費的 Lets' encrypt 證書,并通過腳本在證書過期前自動更新證書。偶爾會出現腳本沒有配置對,或其他的原因導致證書沒有正常更新(尤其是對新部署的站點)。這就需要制作一個功能來解決這類問題。

        下面我們來逐一介紹怎么用 SeaTable 來實現:

        • 站點數據的協同記錄
        • 用腳本實現自動化更新網站證書過期時間
        • 自動化監控和提醒網站證書過期時間
        • 讓表格數據信息可視化

        數據的協同記錄

        關于數據的協同記錄,主要分享以下幾點:

        1. SeaTable 支持日期、圖片、文件、單選、URL、長文本、協作人、創建者、創建時間等豐富的數據類型,用它的數據類型,就可以把運維相關的各種數據類型的信息都集中記錄到表格里。
        2. 當我們把表格的只讀或可讀寫權限,共享給同事后,他們就可以進行只讀或協作編輯了。而且表格管理員還可以根據需要,鎖定表頭、鎖定行、設置列的編輯權限等。比如可以對某列設置任何人都不能編輯、或只有管理員可以編輯、或哪些共享用戶能編輯。
        3. 我們還可以用 API 或者 Python 腳本來同步數據庫中的記錄,或者從第三方抓取數據。

        用腳本來自動化更新網站證書的過期時間

        SeaTable 提供了 Python 腳本的運行環境,我們可以把腳本和數據放在一個地方進行管理,不需要再單獨找一個服務器。同時,可以在表格中根據不同的需要存儲多個 Python 腳本文件, 一鍵點擊運行就可以達到我們想要的效果。如下圖:

        image

        具體的腳本內容這里就不介紹,有興趣了解更多的同學可以訪問 https://seatable.github.io/se...

        腳本除了點擊運行外,還可以安排每日自動運行。

        image

        用提醒規則來自動化提醒

        下面用 SeaTable 的“提醒規則”功能,來實現自動化提醒。

        點擊表格右上角的“提醒規則”按鈕,添加一個提醒規則。比如對“證書過期時間”列的時間,可以設置在還有多少天就要過期時,自動發出提醒通知。另外,在個人微信號綁定了表格賬號的前提下,當這個運維管理表有未閱讀的提醒通知時,如果兩分鐘內你沒有點開網頁并閱讀,那么提醒就會發送到個人微信上。

        提醒規則設置,如下圖:

        image

        靈活查看和可視化

        在靈活查看數據、可視化和統計分析等方面,SeaTable 有表格視圖功能,有日歷、時間線、圖庫、地圖等實用的插件,也有便捷的“統計”功能等。我們可以根據數據特征去選擇使用。比如可以在多視圖間快速切換查看不同角度的數據;利用統計圖表,來對這個記錄了零散數據的網站運維表進行更直觀的動態可視化。

        多視圖:

        image

        統計圖表:

        image

        總結

        我們用 SeaTable 就可以非常方便地在表格里記錄和管理各種類型的數據信息。更重要的是,我們無需再開發工具,用它的“腳本”和“提醒規則”等功能,就快速完成了自動化的數據處理和流程管理。

        作為一款新型的協同表格和低代碼平臺,從使用上來看,它不僅使用門檻低,而且具備靈活性和通用性,即便是非專業技術人員,也能構建自己的業務應用程序,從而不再嚴重依賴技術研發,大幅降低溝通、人力和開發成本。平時我們可以利用它完善的 API、提醒規則和腳本功能等,幫我們快速實現數據處理自動化和業務流程自動化的靈活需求。

        查看原文

        贊 17 收藏 1 評論 3

        波波Nadia 關注了專欄 · 2月23日

        SeaTable開發者版

        SeaTable 開發者版是一款面向開發者的協同表格和低代碼平臺,可私有部署,沒有 API 調用限制,其他功能基本同云服務的免費版本一致。適合于作為企業內部的數據協作中心,實現數據的集中管理、可視化和自動化。https://seatable.cn/developer/

        關注 969

        波波Nadia 贊了文章 · 2月22日

        前端黑科技:美團網頁首幀優化實踐

        前言

        自JavaScript誕生以來,前端技術發展非常迅速。移動端白屏優化是前端界面體驗的一個重要優化方向,Web 前端誕生了 SSR 、CSR、預渲染等技術。在美團支付的前端技術體系里,通過預渲染提升網頁首幀優化,從而優化了白屏問題,提升用戶體驗,并形成了最佳實踐。

        在前端渲染領域,主要有以下幾種方式可供選擇:

        CSR預渲染SSR同構
        優點不依賴數據FP 時間最快客戶端用戶體驗好內存數據共享不依賴數據FCP 時間比 CSR 快客戶端用戶體驗好內存數據共享SEO 友好首屏性能高,FMP 比 CSR 和預渲染快SEO 友好首屏性能高,FMP 比 CSR 和預渲染快客戶端用戶體驗好內存數據共享客戶端與服務端代碼公用,開發效率高
        缺點SEO 不友好FCP 、FMP 慢SEO 不友好FMP 慢客戶端數據共享成本高模板維護成本高Node 容易形成性能瓶頸

        通過對比,同構方案集合 CSR 與 SSR 的優點,可以適用于大部分業務場景。但由于在同構的系統架構中,連接前后端的 Node 中間層處于核心鏈路,系統可用性的瓶頸就依賴于 Node ,一旦作為短板的 Node 掛了,整個服務都不可用。

        結合到我們團隊負責的支付業務場景里,由于支付業務追求極致的系統穩定性,服務不可用直接影響到客訴和資損,因此我們采用瀏覽器端渲染的架構。在保證系統穩定性的前提下,還需要保障用戶體驗,所以采用了預渲染的方式。

        那么究竟什么是預渲染呢?什么是 FCP/FMP 呢?我們先從最常見的 CSR 開始說起。

        以 Vue 舉例,常見的 CSR 形式如下:

        一切看似很美好。然而,作為以用戶體驗為首要目標的我們發現了一個體驗問題:首屏白屏問題。

        為什么會首屏白屏

        瀏覽器渲染包含 HTML 解析、DOM 樹構建、CSSOM 構建、JavaScript 解析、布局、繪制等等,大致如下圖所示:

        要搞清楚為什么會有白屏,就需要利用這個理論基礎來對實際項目進行具體分析。通過 DevTools 進行分析:

        • 等待 HTML 文檔返回,此時處于白屏狀態。
        • 對 HTML 文檔解析完成后進行首屏渲染,因為項目中對 id="app加了灰色的背景色,因此呈現出灰屏。
        • 進行文件加載、JS 解析等過程,導致界面長時間出于灰屏中。
        • 當 Vue 實例觸發了 mounted 后,界面顯示出大體框架。
        • 調用 API 獲取到時機業務數據后才能展示出最終的頁面內容。

        由此得出結論,因為要等待文件加載、CSSOM 構建、JS 解析等過程,而這些過程比較耗時,導致用戶會長時間出于不可交互的首屏灰白屏狀態,從而給用戶一種網頁很“慢”的感覺。那么一個網頁太“慢”,會造成什么影響呢?

        “慢”的影響

        Global Web Performance Matters for ecommerce的報告中指出:

        • 57%的用戶更在乎網頁在3秒內是否完成加載。
        • 52%的在線用戶認為網頁打開速度影響到他們對網站的忠實度。
        • 每慢1秒造成頁面 PV 降低11%,用戶滿意度也隨之降低降低16%。
        • 近半數移動用戶因為在10秒內仍未打開頁面從而放棄。

        我們團隊主要負責美團支付相關的業務,如果網站太慢會影響用戶的支付體驗,會造成客訴或資損。既然網站太“慢”會造成如此重要的影響,那要如何優化呢?

        優化思路

        User-centric Performance Metrics一文中,共提到了4個頁面渲染的關鍵指標:

        基于這個理論基礎,再回過頭來看看之前項目的實際表現:

        可見在 FP 的灰白屏界面停留了很長時間,用戶不清楚網站是否有在正常加載,用戶體驗很差。

        試想:如果我們可以將 FCP 或 FMP 完整的 HTML 文檔提前到 FP 時機預渲染,用戶看到頁面框架,能感受到頁面正在加載而不是冷冰冰的灰白屏,那么用戶更愿意等待頁面加載完成,從而降低了流失率。并且這種改觀在弱網環境下更明顯。

        通過對比 FP、FCP、FMP 這三個時期 DOM 的差異,發現區別在于:



        • FP:僅有一個 div 根節點。
        • FCP:包含頁面的基本框架,但沒有數據內容。
        • FMP:包含頁面所有元素及數據。

        仍然以 Vue 為例, 在其生命周期中,mounted 對應的是 FCP,updated 對應的是 FMP。那么具體應該使用哪個生命周期的 HTML 結構呢?

        mounted (FCP)updated (FMP)
        缺點只是視覺體驗將 FCP 提前,實際的 TTI 時間變化不大構建時需要獲取數據,編譯速度慢構建時與運行時的數據存在差異性有復雜交互的頁面,仍需等待,實際的 TTI 時間變化不大
        優點不受數據影響,編譯速度快首屏體驗好對于純展示類型的頁面,FP 與 TTI 時間近乎一致

        通過以上的對比,最終選擇在 mounted 時觸發構建時預渲染。由于我們采用的是 CSR 的架構,沒有 Node 作為中間層,因此要實現 DOM 內容的預渲染,就需要在項目構建編譯時完成對原始模板的更新替換。

        至此,我們明確了構建時預渲染的大體方案。

        構建時預渲染方案

        構建時預渲染流程:

        配置讀取

        由于 SPA 可以由多個路由構成,需要根據業務場景決定哪些路由需要用到預渲染。因此這里的配置文件主要是用于告知編譯器需要進行預渲染的路由。

        在我們的系統架構里,腳手架是基于 Webpack 自研的,在此基礎上可以自定義自動化構建任務和配置。

        觸發構建

        項目中主要是使用 TypeScript,利用 TS 的裝飾器,我們封裝了統一的預渲染構建的鉤子方法,從而只用一行代碼即可完成構建時預渲染的觸發。

        裝飾器:

        使用:

        構建編譯

        從流程圖上,需要在發布機上啟動模擬的瀏覽器環境,并通過預渲染的事件鉤子獲取當前的頁面內容,生成最終的 HTML 文件。

        由于我們在預渲染上的嘗試比較早,當時還沒有 Headless Chrome 、 Puppeteer、Prerender SPA Plugin等,因此在選型上使用的是 phantomjs-prebuilt(Prerender SPA Plugin 早期版本也是基于 phantomjs-prebuilt 實現的)。

        通過 phantom 提供的 API 可獲得當前 HTML,示例如下:

        為了提高構建效率,并行對配置的多個頁面或路由進行預渲染構建,保證在 5S 內即可完成構建,流程圖如下:

        方案優化

        理想很豐滿,現實很骨感。在實際投產中,構建時預渲染方案遇到了一個問題。

        我們梳理一下簡化后的項目上線過程:

        開發 -> 編譯 -> 上線

        假設本次修改了靜態文件中的一個 JS 文件,這個文件會通過 CDN 方式在 HTML 里引用,那么最終在 HTML 文檔中的引用方式是 <script data-original="http://cdn.com/index.js"></script>。然而由于項目還沒有上線,所以其實通過完整 URL 的方式是獲取不到這個文件的;而預渲染的構建又是在上線動作之前,所以問題就產生了:

        構建時預渲染無法正常獲取文件,導致編譯報錯

        怎么辦?

        請求劫持

        因為在做預渲染時,我們使用啟動了一個模擬的瀏覽器環境,根據 phantom 提供的 API,可以對發出的請求加以劫持,將獲取 CDN 文件的請求劫持到本地,從而在根本上解決了這個問題。示例代碼如下:

        構建時預渲染研發流程及效果

        最終,構建時預渲染研發流程如下:

        開發階段:

        • 通過 TypeScript 的裝飾器單行引入預渲染構建觸發的方法。
        • 發布前修改編譯構建的配置文件。

        發布階段:

        • 先進行常規的項目構建。
        • 若有預渲染相關配置,則觸發預渲染構建。
        • 通過預渲染得到最終的文件,并完成發布上線動作。

        完整的用戶請求路徑如下:

        通過構建時預渲染在項目中的使用,FCP 的時間相比之前減少了 75%。

        作者簡介

        寒陽,美團資深研發工程師,多年前端研發經歷,負責美團支付錢包團隊和美團支付前端基礎技術。

        招聘信息

        我們美團金融服務平臺大前端研發組在高速成長中,我們歡迎更多優秀的 Web 前端研發工程師加入,感興趣的朋友可以將簡歷發送到郵箱:shanghanyang@meituan.com。

        查看原文

        贊 140 收藏 97 評論 16

        波波Nadia 贊了文章 · 2月19日

        火星無人機全部代碼公開!毅力號帶著手機芯片和 Linux 系統上太空

        毅力號登陸火星,帶著手機芯片和 Linux 系統上太空了!

        歷經 203 天,穿越了 4.72 億公里之后,美國“毅力號”火星車終于在美東時間下午 3:55 成功登陸火星。

        結束近 7 個月的旅程后,“毅力號”傳回了通過避險攝像機拍攝的第一張火星表面景象。這次,“毅力號”的主要任務是——尋找古代生命的跡象,并收集火星巖石和土壤樣本帶回地球研究。

        值得一提的是,配合“毅力號”完成探測任務的“機智號”無人機搭載的是驍龍 801 處理器。沒錯,就是那個用在手機上的驍龍 801。當年,小米 4 用的就是這款芯片。

        此外,這也是人類第一次在火星上運行 Linux 系統?!耙懔μ枴鄙系臒o人機“機智號”實際上是通過 Linux 操作系統控制的。不止如此,NASA 還把這個專門為火星無人機開發的 Linux 飛行控制系統開源了!

        這就是毅力號在火星表面拍攝的第一張圖像:

        image.png

        “恐怖 7 分鐘”艱難著陸

        2020 年 7 月 30 日,耗資 24 億美元的毅力號從美國佛羅里達州的卡納維拉爾角太空部隊站發射升空,帶著收集火星樣本的任務邁出了火星探索的第一步。

        美國宇航局科學副主任托馬斯說,“毅力號是從火星帶回巖石的第一步,我們不知道這些來自火星的原始樣本會告訴我們什么,但無疑是非常重要的,甚至可能包括曾經存在于地球之外的生命?!?/p>

        image.png

        毅力號進入下降階段時,以大約 20000 km/h 的速度飛行,盡管火星的氣氛很稀薄,但它仍將給毅力號帶來極大的阻力。進入火星大氣層大約 80 秒鐘之內,航空器外殼外部的溫度將達到 1300 攝氏度。

        約四分鐘后,毅力號的“降落傘”展開,保護性航空器外殼脫落。當毅力號下降到火星表面上方約 4 公里時,它將激活其地形導航系統。

        410 秒后(將近 7 分鐘),毅力號終于在火星成功著陸。與 2018 年 8 月的“好奇號”火星車非常相似,它也在著陸時經歷了類似的“恐怖 7 分鐘”。

        image.png

        火星表面首次有直升機起飛

        毅力號首次將直升機帶上了火星,機智號火星無人機將在火星表面飛起幾英尺的高度,并在毅力號火星車的周圍盤旋,收集圖像信息。這將是直升機在火星極薄的大氣層中首次實現動力飛行。

        image.png

        機智號無人機僅重 1.8 公斤,通過頂部安裝的 4 個碳纖維螺旋槳提供動力,每分鐘轉速為 2400 轉,功率為 350 瓦。為了配合毅力號的探測任務,它要面對許多挑戰。

        要知道,實現直升機在火星上飛行是有很大難度的。一方面火星的稀薄大氣使得難以獲得足夠的升力。另一方面由于火星大氣層的密度比地球密度低 99%,直升機的旋轉葉片也要做的更大,并且轉速要非??觳拍芷痫w。

        image.png

        機智號采用驍龍 801 處理器,帶著 Linux 系統上火星

        由于太空探索對硬件設備的穩定性要求極高,很多設備都采用了已經在地面運行了多年的處理器,機智號也是如此。但值得注意的是,機智號這次沒有采用商業級別的處理器,而是用于手機的民用處理器。這是因為,機智號被 NASA 視為一項“技術演示”,因此愿意接受更多風險,于是采用了民用的驍龍 801 處理器。

        image.png

        此外,由于毅力號的任務對信息的收集和處理要求極高,需要捕捉圖像、分析特征,并以 30 赫茲的頻率從一幀到另一幀跟蹤它們。以往已經使用多年的商業級處理器已無法達到標準。而驍龍 801 的本質是一款手機處理器,而且它的主板非常小。它的功能遠比其他火星車上的處理器多得多,擁有更強大的計算力。

        除了手機處理器,機智號還帶來了一個驚喜,將 Linux 帶上了火星。

        這是人類第一次在火星上使用 Linux 飛行控制系統,據 NASA 介紹,機智號使用的軟件框架是JPL 為立方體衛星和儀器開發的,并在幾年前就開源了。也就是說,任何人都能使用這個在火星直升機上的軟件框架,并將它用在你自己的項目上。

        將開源進行到底,火星無人機代碼已全部公開

        F Prime 是火星無人機“機智號”的飛行軟件框架,目前已在 GitHub 上全部公開!

        F Prime 是為機智號量身定制的一個組件驅動的框架,可以快速開發和部署太空飛行及其他嵌入式軟件應用程序。

        那么,有了這些公開的代碼,我們是不是也能下載機智號同款代碼搞個火星無人機出來了呢?

        image.png

        NASA 開源的 F Prime 提供了一個完整的開發生態系統,包括建模工具、測試工具和地面數據系統。開發人員使用建模工具編寫高級規范,自動使用 C ++ 生成實現,并使用特定領域的代碼填充實現??蚣芎痛a生成器提供 F Prime 部署所需的所有樣板代碼,包括用于線程管理的代碼,用于組件之間通信的代碼以及用于處理命令,遙測和參數的代碼。測試工具和地面數據系統簡化了在工作站和實驗室中的飛行硬件上的軟件測試。

        此外,F Prime 還實現了以下幾個關鍵功能:

        1.可重用性:基于組件的體系結構可實現高度的模塊化和軟件重用。

        2.可移植性:F Prime 在從微控制器到多核計算機的多種處理器以及多種操作系統上運行,將其移植到新的操作系統非常簡單。

        3.高性能:采用點對點架構,最大程度地減少了計算資源的使用,非常適合較小的處理器。

        4.量身定制,可滿足小型任務所需的復雜程度,不僅易于使用,還能同時仍支持多種任務。

        5.可分析性:類型化的端口連接為編譯時的正確性提供了有力的保證。

        快速安裝指南

        前提條件:

        • cmake
        • git
        • Python 3.5+ with pip

        安裝這些實用程序后,即可安裝 F Prime Python 依賴項。在 Python 虛擬環境中安裝依賴項可以防止系統級問題,但是不需要在虛擬環境中進行安裝。

        要快速安裝 F Prime,請輸入:

        image.png

        太空冒險邁上新臺階,“移民火星”不是夢

        毅力號將在火星完成一系列高度復雜的任務,為人類探索古代生物信息和火星土壤研究提供有力支持。隨著毅力號一起登陸火星的機智號也為人類的太空事業邁上了一個更高的臺階。

        與此同時,中國的“天問一號”火星車也即將今年 5 月登陸火星。人類的太空冒險仍在繼續,也許“移民火星”在未來的某一天真的將不止是夢想,而真正照進現實。

        參考鏈接:https://spectrum.ieee.org/aut...
        https://www.futurezone.de/sci...
        GitHub 地址:https://github.com/nasa/fprime

        segmentfault 公眾號

        查看原文

        贊 8 收藏 1 評論 1

        波波Nadia 關注了用戶 · 2月19日

        薯條 @shutiao_5fd752222e7b7

        個人微信公眾號:「薯條的自我修養」,歡迎關注

        關注 1137

        波波Nadia 贊了文章 · 2月19日

        如何提高代碼的可讀性 學習筆記

        本文整理自 taowen 師傅在滴滴內部的分享。

        1.Why

        對一線開發人員來說,每天工作內容大多是在已有項目的基礎上繼續堆代碼。當項目實在堆不動時就需要尋找收益來重構代碼。既然我們的大多數時間都花在坐在顯示器前讀寫代碼這件事上,那可讀性不好的代碼都是在謀殺自己or同事的生命,所以不如一開始就提煉技巧,努力寫好代碼; )

        2.How

        為提高代碼可讀性,先來分析代碼實際運行環境。代碼實際運行于兩個地方:cpu人腦。對于cpu,代碼優化需理解其工作機制,寫代碼時為針對cpu特性進行優化;對于人腦,我們在讀代碼時,它像解釋器一樣,一行一行運行代碼,從這個角度來說,要提高代碼的可讀性首先需要知道大腦的運行機制。

        image.png

        下面來看一下人腦適合做的事情和不適合做的事情:

        大腦擅長做的事情

        名稱圖片說明
        對象識別image.png不同于機器學習看無數張貓片之后可能還是不能準確識別貓這個對象,人腦在看過幾只貓之后就可以很好的識別。
        空間分解image.png人腦不需要標注,可以直觀感受到空間中的不同物體。
        時序預測image.png你的第一感覺是不是這個哥們要被車撞了?
        時序記憶image.png作為人類生存本能之一,我們多次走過某個地方時,人腦會對這個地方形成記憶。
        類比推測image.png人腦還有類比功能,比如說這道題大多數人會選擇C吧。

        大腦不擅長做的事情

        名稱圖片例子
        無法映射到現實生活經驗的抽象概念image.png人腦看到左圖時,會比較輕松想到通關方式,但是如果換成右圖這種抽象的概念,里面的對象換成了嘿嘿的像素,我們就不知道這是什么鬼了。比如說代碼里如果充斥著Z,X,C,V 這樣的變量名,你可能就看懵了。
        冗長的偵探推理image.png這種需要遞歸(or循環)去檢查所有可能性最后找到解法的場景,人腦同樣不擅長。
        跟蹤多個同時變化的過程image.png大腦是個單線程的CPU,不擅長左手畫圓,右手畫圈。

        代碼優化理論

        了解人腦的優缺點后,寫代碼時就可以根據人腦的特點對應改善代碼的可讀性了。這里提取出三種理論:

        1. Align Models ,匹配模型:代碼中的數據和算法模型 應和人腦中的 心智模型對應
        2. Shorten Process , 簡短處理:寫代碼時應 縮短 “福爾摩斯探案集” 的流程長度,即不要寫大段代碼
        3. Isolate Process,隔離處理:寫代碼一個流程一個流程來處理,不要同時描述多個流程的演進過程

        下面通過例子詳細解釋這三種模型:

        Align Models

        在代碼中,模型無外乎就是數據結構算法,而在人腦中,對應的是心智模型,所謂心智模型就是人腦對于一個物體 or 一件事情的想法,我們平時說話就是心智模型的外在表現。寫代碼時應把代碼中的名詞與現實名詞對應起來,減少人腦從需求文檔到代碼的映射成本。比如對于“銀行賬戶”這個名詞,很多變量名都可以體現這個詞,比如:bankAccount、bank_account、account、BankAccount、BA、bank_acc、item、row、record、model,編碼中應統一使用和現實對象能鏈接上的變量名。

        代碼命名技巧

        起變量名時候取其實際含義,沒必要隨便寫個變量名然后在注釋里面偷偷用功。

        // bad
        var d int // elapsed time in days
        
        // good
        var elapsedTimeInDays int // 全局使用

        起函數名 動詞+名詞結合,還要注意標識出你的自定義變量類型:

        // bad
        func getThem(theList [][]int) [][]int {
            var list1 [][]int // list1是啥,不知道
            for _, x := range theList {
                if x[0] == 4 { // 4是啥,不知道
                    list1 = append(list1, x)
                }
            }
            return list1
        }
        
        // good
        type Cell []int // 標識[]int作用
        
        func (cell Cell) isFlagged() bool { // 說明4的作用
            return cell[0] == 4
        }
        
        func getFlaggedCells(gameBoard []Cell) []Cell { // 起有意義的變量名
            var flaggedCells []Cell
            for _, cell := range gameBoard {
                if cell.isFlagged() {
                    flaggedCells = append(flaggedCells, cell)
                }
            }
            return flaggedCells
        }
        代碼分解技巧

        按照空間分解(Spatial Decomposition):下面這塊代碼都是與Page相關的邏輯,仔細觀察可以根據page的空間分解代碼:

        // bad
        // …then…and then … and then ... // 平鋪直敘描述整個過程
        func RenderPage(request *http.Request) map[string]interface{} {
            page := map[string]interface{}{}
            name := request.Form.Get("name")
            page["name"] = name
            urlPathName := strings.ToLower(name)
            urlPathName = regexp.MustCompile(`['.]`).ReplaceAllString(
                urlPathName, "")
            urlPathName = regexp.MustCompile(`[^a-z0-9]+`).ReplaceAllString(
                urlPathName, "-")
            urlPathName = strings.Trim(urlPathName, "-")
            page["url"] = "/biz/" + urlPathName
            page["date_created"] = time.Now().In(time.UTC)
            return page
        }
        // good
        // 按空間分解,這樣的好處是可以集中精力到關注的功能上
        var page = map[string]pageItem{
            "name":         pageName,
            "url":          pageUrl,
            "date_created": pageDateCreated,
        }
        
        type pageItem func(*http.Request) interface{}
        
        func pageName(request *http.Request) interface{} { // name 相關過程
            return request.Form.Get("name")
        }
        
        func pageUrl(request *http.Request) interface{} { // URL 相關過程
            name := request.Form.Get("name")
            urlPathName := strings.ToLower(name)
            urlPathName = regexp.MustCompile(`['.]`).ReplaceAllString(
                urlPathName, "")
            urlPathName = regexp.MustCompile(`[^a-z0-9]+`).ReplaceAllString(
                urlPathName, "-")
            urlPathName = strings.Trim(urlPathName, "-")
            return "/biz/" + urlPathName
        }
        
        func pageDateCreated(request *http.Request) interface{} { // Date 相關過程
            return time.Now().In(time.UTC)
        }

        按照時間分解(Temporal Decomposition):下面這塊代碼把整個流程的算賬和打印賬單混寫在一起,可以按照時間順序對齊進行分解:

        // bad 
        func (customer *Customer) statement() string {
            totalAmount := float64(0)
            frequentRenterPoints := 0
            result := "Rental Record for " + customer.Name + "\n"
        
            for _, rental := range customer.rentals {
                thisAmount := float64(0)
                switch rental.PriceCode {
                case REGULAR:
                    thisAmount += 2
                case New_RELEASE:
                    thisAmount += rental.rent * 2
                case CHILDREN:
                    thisAmount += 1.5
                }
                frequentRenterPoints += 1
                totalAmount += thisAmount
            }
            result += strconv.FormatFloat(totalAmount,'g',10,64) + "\n"
            result += strconv.Itoa(frequentRenterPoints)
        
            return result
        }
        // good 邏輯分解后的代碼
        func statement(custom *Customer) string {
            bill := calcBill(custom)
        
            statement := bill.print()
        
            return statement
        }
        
        type RentalBill struct {
            rental Rental
            amount float64
        }
        
        type Bill struct {
            customer             *Customer
            rentals              []RentalBill
            totalAmount          float64
            frequentRenterPoints int
        }
        
        func calcBill(customer *Customer) Bill {
        
            bill := Bill{}
            for _, rental := range customer.rentals {
                rentalBill := RentalBill{
                    rental: rental,
                    amount: calcAmount(rental),
                }
                bill.frequentRenterPoints += calcFrequentRenterPoints(rental)
                bill.totalAmount += rentalBill.amount
                bill.rentals = append(bill.rentals, rentalBill)
            }
            return bill
        }
        
        func (bill Bill) print() string {
        
            result := "Rental Record for " + bill.customer.name + "(n"
        
            for _, rental := range bill.rentals{
                result += "\t" + rental.movie.title + "\t" +
                    strconv.FormatFloat(rental.amount, 'g', 10, 64) + "\n"
            }
            
        
            result += "Amount owed is " +
                strconv.FormatFloat(bill.totalAmount, 'g', 10, 64) + "\n"
        
            result += "You earned + " +
                strconv.Itoa(bill.frequentRenterPoints) + "frequent renter points"
        
            return result
        }
        
        func calcAmount(rental Rental) float64 {
            thisAmount := float64(0)
            switch rental.movie.priceCode {
            case REGULAR:
                thisAmount += 2
                if rental.daysRented > 2 {
                    thisAmount += (float64(rental.daysRented) - 2) * 1.5
                }
            case NEW_RELEASE:
                thisAmount += float64(rental.daysRented) * 3
            case CHILDRENS:
                thisAmount += 1.5
                if rental.daysRented > 3 {
                    thisAmount += (float64(rental.daysRented) - 3) * 1.5
                }
            }
            return thisAmount
        }
        
        func calcFrequentRenterPoints(rental Rental) int {
            frequentRenterPoints := 1
            switch rental.movie.priceCode {
            case NEW_RELEASE:
                if rental.daysRented > 1 {
                    frequentRenterPointst++
                }
            }
            return frequentRenterPoints
        }

        按層分解(Layer Decomposition):

        // bad
        func findSphericalClosest(lat float64, lng float64, locations []Location) *Location {
            var closest *Location
          closestDistance := math.MaxFloat64
          for _, location := range locations {
            latRad := radians(lat)
            lngRad := radians(lng)
            lng2Rad := radians(location.Lat)
            lng2Rad := radians(location.Lng)
            var dist = math.Acos(math.Sin(latRad) * math.Sin(lat2Rad) +  
                                 math.Cos(latRad) * math.Cos(lat2Rad) *
                                 math.Cos(lng2Rad - lngRad) 
                                )
            if dist < closestDistance {
                    closest = &location
              closestDistance = dist
            }
          }
            return closet
        }
        // good
        type Location struct {
        }
        
        type compare func(left Location, right Location) int
        
        func min(objects []Location, compare compare) *Location {
            var min *Location
            for _, object := range objects {
                if min == nil {
                    min = &object
                    continue
                }
                if compare(object, *min) < 0 {
                    min = &object
                }
            }
            return min
        }
        
        func findSphericalClosest(lat float64, lng float64, locations []Location) *Location {
            isCloser := func(left Location, right Location) int {
                leftDistance := rand.Int()
                rightDistance := rand.Int()
                if leftDistance < rightDistance {
                    return -1
                } else {
                    return 0
                }
            }
            closet := min(locations, isCloser)
            return closet
        }
        注釋

        注釋不應重復代碼的工作。應該去解釋代碼的模型和心智模型的映射關系,應說明為什么要使用這個代碼模型,下面的例子就是反面教材:

        // bad
        /** the name. */
        var name string
        /** the version. */
        var Version string
        /** the info. */
        var info string
        
        // Find the Node in the given subtree, with the given name, using the given depth.
        func FindNodeInSubtree(subTree *Node, name string, depth *int) *Node {
        }

        下面的例子是正面教材:

        // Impose a reasonable limit - no human can read that much anyway
        const MAX_RSS_SUBSCRIPTIONS = 1000
        
        // Runtime is O(number_tags * average_tag_depth), 
        // so watch out for badly nested inputs.
        func FixBrokenHTML(HTML string) string {
            // ...
        }

        Shorten Process

        Shorten Process的意思是要縮短人腦“編譯代碼”的流程。應該避免寫出像小白鼠走迷路一樣又長又繞的代碼。所謂又長又繞的代碼表現在,跨表達式跟蹤、跨多行函數跟蹤、跨多個成員函數跟蹤、跨多個文件跟蹤、跨多個編譯單元跟蹤,甚至是跨多個代碼倉庫跟蹤。

        對應的手段可以有:引入變量、拆分函數、提早返回、縮小變量作用域,這些方法最終想達到的目的都是讓大腦喘口氣,不要一口氣跟蹤太久。同樣來看一些具體的例子:

        例子

        下面的代碼,多種復合條件組合在一起,你看了半天繞暈了可能也沒看出到底什么情況下為true,什么情況為false。

        // bad
        func (rng *Range) overlapsWith(other *Range) bool {
            return (rng.begin >= other.begin && rng.begin < other.end) ||
                (rng.end > other.begin && rng.end <= other.end) ||
                (rng.begin <= other.begin && rng.end >= other.end)
        }

        但是把情況進行拆解,每種條件進行單獨處理。這樣邏輯就很清晰了。

        // good
        func (rng *Range) overlapsWith(other *Range) bool {
            if other.end < rng.begin {
                return false // they end before we begin 
            }    
            if other.begin >= rng.end {
                return false // they begin after we end 
            }
          return true // Only possibility left: they overlap
        }

        再來看一個例子,一開始你寫代碼的時候,可能只有一個if ... else...,后來PM讓加一下權限控制,于是你可以開心的在if里繼續套一層if,補丁打完,開心收工,于是代碼看起來像這樣:

        // bad 多層縮進的問題
        func handleResult(reply *Reply, userResult int, permissionResult int) {
          if userResult == SUCCESS {
            if permissionResult != SUCCESS {
              reply.WriteErrors("error reading permissions")
                reply.Done()
                return
            }
            reply.WriteErrors("")
          } else {
            reply.WriteErrors("User Result")
          }
          reply.Done()
        }

        這種代碼也比較好改,一般反向寫if條件返回判否邏輯即可:

        // good
        func handleResult(reply *Reply, userResult int, permissionResult int) {
          defer reply.Done()
          if userResult != SUCCESS {
            reply.WriteErrors("User Result")
            return 
          }
          if permissionResult != SUCCESS {
            reply.WriteErrors("error reading permissions")
            return
          }
          reply.WriteErrors("")
        }

        這個例子的代碼問題比較隱晦,它的問題是所有內容都放在了MooDriver這個對象中。

        // bad
        type MooDriver struct {
            gradient Gradient
          splines []Spline
        }
        func (driver *MooDriver) drive(reason string) {
          driver.saturateGradient()
          driver.reticulateSplines()
          driver.diveForMoog(reason)
        }

        比較好的方法是盡可能減少全局scope,而是使用上下文變量進行傳遞。

        // good 
        type ExplicitDriver struct {
          
        }
        
        // 使用上下文傳遞
        func (driver *MooDriver) drive(reason string) {
          gradient := driver.saturateGradient()
          splines := driver.reticulateSplines(gradient)
          driver.diveForMoog(splines, reason)
        }

        Isolate Process

        人腦缺陷是不擅長同時跟蹤多件事情,如果”同時跟蹤“事物的多個變化過程,這不符合人腦的構造;但是如果把邏輯放在很多地方,這對大腦也不友好,因為大腦需要”東拼西湊“才能把一塊邏輯看全。所以就有了一句很經典的廢話,每個學計算機的大學生都聽過。你的代碼要做到高內聚,低耦合,這樣就牛逼了!-_-|||,但是你要問說這話的人什么叫高內聚,低耦合呢,他可能就得琢磨琢磨了,下面來通過一些例子來琢磨一下。

        首先先來玄學部分,如果你的代碼寫成下面這樣,可讀性就不會很高。

        image.png

        一般情況下,我們可以根據業務場景努力把代碼修改成這樣:

        image.png

        舉幾個例子,下面這段代碼非常常見,里面version的含義是用戶端上不同的版本需要做不同的邏輯處理。

        func (query *Query) doQuery() {
          if query.sdQuery != nil {
            query.sdQuery.clearResultSet()
          }
          // version 5.2 control
          if query.sd52 {
            query.sdQuery = sdLoginSession.createQuery(SDQuery.OPEN_FOR_QUERY)
          } else {
            query.sdQuery = sdSession.createQuery(SDQuery.OPEN_FOR_QUERY)
          }
          query.executeQuery()
        }

        這段代碼的問題是由于版本差異多塊代碼流程邏輯Merge在了一起,造成邏輯中間有分叉現象。處理起來也很簡單,封裝一個adapter,把版本邏輯抽出一個interface,然后根據版本實現具體的邏輯。

        再來看個例子,下面代碼中根據expiry和maturity這樣的產品邏輯不同 也會造成分叉現象,所以你的代碼會寫成這樣:

        // bad
        type Loan struct {
            start    time.Time
            expiry   *time.Time
            maturity *time.Time
            rating   int
        }
        
        func (loan *Loan) duration() float64 {
            if loan.expiry == nil {
                return float64(loan.maturity.Unix()-loan.start.Unix()) / 365 * 24 * float64(time.Hour)
            } else if loan.maturity == nil {
                return float64(loan.expiry.Unix()-loan.start.Unix()) / 365 * 24 * float64(time.Hour)
            }
            toExpiry := float64(loan.expiry.Unix() - loan.start.Unix())
            fromExpiryToMaturity := float64(loan.maturity.Unix() - loan.expiry.Unix())
            revolverDuration := toExpiry / 365 * 24 * float64(time.Hour)
            termDuration := fromExpiryToMaturity / 365 * 24 * float64(time.Hour)
            return revolverDuration + termDuration
        }
        
        func (loan *Loan) unusedPercentage() float64 {
            if loan.expiry != nil && loan.maturity != nil {
                if loan.rating > 4 {
                    return 0.95
                } else {
                    return 0.50
                }
            } else if loan.maturity != nil {
                return 1
            } else if loan.expiry != nil {
                if loan.rating > 4 {
                    return 0.75
                } else {
                    return 0.25
                }
            }
            panic("invalid loan")
        }

        解決多種產品邏輯的最佳實踐是Strategy pattern,代碼入下圖,根據產品類型創建出不同的策略接口,然后分別實現duration和unusedPercentage這兩個方法即可。

        // good
        type LoanApplication struct {
            expiry   *time.Time
            maturity *time.Time
        }
        
        type CapitalStrategy interface {
            duration() float64
            unusedPercentage() float64
        }
        
        func createLoanStrategy(loanApplication LoanApplication) CapitalStrategy {
            if loanApplication.expiry != nil && loanApplication.maturity != nil {
                return createRCTL(loanApplication)
            }
            if loanApplication.expiry != nil {
                return createRevolver(loanApplication)
            }
            if loanApplication.maturity != nil {
                return createTermLoan
            }
            panic("invalid loan application")
        }

        但是現實情況沒有這么簡單,因為不同事物在你眼中就是多進程多線程運行的,比如上面產品邏輯的例子,雖然通過一些設計模式把執行的邏輯隔離到了不同地方,但是代碼中只要含有多種產品,代碼在執行時還是會有一個產品選擇的過程。邏輯發生在同一時間、同一空間,所以“自然而然”就需要寫在了一起:

        • 功能展示時,由于需要展示多種信息,會造成 concurrent process
        • 寫代碼時,業務包括功能性和非功能性需求,也包括正常邏輯和異常邏輯處理
        • 考慮運行效率時,為提高效率我們會考慮異步I/O、多線程/協程
        • 考慮流程復用時,由于版本差異和產品策略也會造成merged concurrent process

        對于多種功能雜糅在一起,比如上面的RenderPage函數,對應解法為不要把所有事情合在一起搞,把單塊功能內聚,整體再耦合成為一個單元。

        對于多個同步進行的I/O操作,可以通過協程把揉在一起的過程分開來:

        // bad 兩個I/O寫到一起了
        func sendToPlatforms() {
            httpSend("bloomberg", func(err error) {
                if err == nil {
                    increaseCounter("bloomberg_sent", func(err error) {
                        if err != nil {
                            log("failed to record counter", err)
                        }
                    })
                } else {
                    log("failed to send to bloom berg", err)
                }
            })
            ftpSend("reuters", func(err error) {
                if err == DIRECTORY_NOT_FOUND {
                    httpSend("reuterHelp", err)
                }
            })
        }

        對于這種并發的I/O場景,最佳解法就是給每個功能各自寫一個計算函數,代碼真正運行的時候是”同時“在運行,但是代碼中是分開的。

        //good 協程寫法
        func sendToPlatforms() {
            go sendToBloomberg()
            go sendToReuters()
        }
        
        func sendToBloomberg() {
            err := httpSend("bloomberg")
            if err != nil {
                log("failed to send to bloom berg", err)
                return
            }
            err := increaseCounter("bloomberg_sent")
            if err != nil {
                log("failed to record counter", err)
            }
        }
        
        func sendToReuters() {
            err := ftpSend("reuters")
            if err == nil {
                httpSend("reutersHelp", err)
            }
        }

        有時,邏輯必須要合并到一個Process里面,比如在買賣商品時必須要對參數做邏輯檢查:

        // bad
        func buyProduct(req *http.Request) error {
            err := checkAuth(req)
            if err != nil {
                return err
            }
            // ...
        }
        
        func sellProduct(req *http.Request) error {
            err := checkAuth(req)
            if err != nil {
                return err
            }
            // ...
        }

        這種頭部有公共邏輯經典解法是寫個Decorator單獨處理權限校驗邏輯,然后wrapper一下正式邏輯即可:

        // good 裝飾器寫法
        func init() {
            buyProduct = checkAuthDecorator(buyProduct)
            sellProduct = checkAuthDecorator(sellProduct)
        }
        
        func checkAuthDecorator(f func(req *http.Request) error) func(req *http.Request) error {
            return func(req *http.Request) error {
                err := checkAuth(req)
                if err != nil {
                    return err
                }
                return f(req)
            }
        }
        
        var buyProduct = func(req *http.Request) error {
            // ...
        }
        
        var sellProduct = func(req *http.Request) error {
            // ...
        }

        此時你的代碼會想這樣:

        image.png

        當然公共邏輯不僅僅存在于頭部,仔細思考一下所謂的strategy、Template pattern,他們是在邏輯的其他地方去做這樣的邏輯處理。

        image.png

        這塊有一個新的概念叫:信噪比。信噪比是一個相對概念,信息,對有用的;噪音,對沒用的。代碼應把什么邏輯寫在一起,不僅取決于讀者是誰,還取決于這個讀者當時希望完成什么目標。

        比如下面這段C++和Python代碼:

        void sendMessage(const Message &msg) const {...}
        ![image.png](/img/bVcOMhy)
        
        def sendMessage(msg):

        如果你現在要做業務開發,你可能會覺得Python代碼讀起來很簡潔;但是如果你現在要做一些性能優化的工作,C++代碼顯然能給你帶來更多信息。

        再比如下面這段代碼,從業務邏輯上講,這段開發看起來非常清晰,就是去遍歷書本獲取Publisher。

        for _, book := range books {
          book.getPublisher()
        }

        但是如果你看了線上打了如下的SQL日志,你懵逼了,心想這個OOM真**,真就是一行一行執行SQL,這行代碼可能會引起DB報警,讓你的DBA同事半夜起來修DB。

        SELECT * FROM Pubisher WHERE PublisherId = book.publisher_id
        SELECT * FROM Pubisher WHERE PublisherId = book.publisher_id
        SELECT * FROM Pubisher WHERE PublisherId = book.publisher_id
        SELECT * FROM Pubisher WHERE PublisherId = book.publisher_id
        SELECT * FROM Pubisher WHERE PublisherId = book.publisher_id

        所以如果代碼改成這樣,你可能就會更加明白這塊代碼其實是在循環調用實體。

        for _, book := range books {
          loadEntity("publisher", book.publisher_id)
        }

        總結一下:

        • 優先嘗試給每 個Process一個自己的函數,不要合并到一起來算

          • 嘗試界面拆成組件
          • 嘗試把訂單拆成多個單據,獨立跟蹤多個流程
          • 嘗試用協程而不是回調來表達concurrent i/o
        • 如果不得不在一個Process中處理多個相對獨立的事情

          • 嘗試復制一份代碼,而不是復用同一個Process
          • 嘗試顯式插入: state/ adapter/ strategy/template/ visitor/ observer
          • 嘗試隱式插入: decorator/aop
          • 提高信噪比是相對于具體目標的,提高了一個目標的信噪比,就降低了另外一個目標的信噪比

        3.總結

        當我們吐槽這塊代碼可讀性太差時,不要把可讀性差的原因簡單歸結為注釋不夠 或者不OO,而是可以從人腦特性出發,根據下面的圖片去找到代碼問題,然后試著改進它(跑了幾年的老代碼還是算了,別改一行線上全炸了: )

        image.png

        查看原文

        贊 22 收藏 5 評論 0

        波波Nadia 贊了文章 · 2月15日

        全世界第二受歡迎的 React UI 組件庫,一夜之間不見了...

        Ant Design

        據 Ant Design 官方賬號表示,Ant Design 在 Github 上的倉庫在沒有任何原因的情況下突然消失了,團隊正在聯系 GitHub 官方尋求幫助和反饋。

        截止發稿時間, Ant Design 官方 GitHub 賬號頁面仍然顯示 404。

        全世界第二受歡迎的 React UI 組件庫

        Ant Design 是螞蟻金服推出的一套企業級 UI 設計語言和 React 組件庫,從 2015 年推出開始便受到廣泛的關注與使用,Ant Design 官網的標題也設置為: 全世界第二受歡迎的 React UI 組件庫。

        作為一個設計體系,Ant Design 包含的不僅僅是一個組件庫。除了耳熟能詳的 Ant Design React 外,還有 Angular 版本的 NG-ZORRO、Ant Desin Mobile、Ant Design Landing、HiTu React,以及來自社區志愿者的 Ant Design Vue。

        垂直方向開箱即用的中臺前端 / 設計解決方案 Ant Design Pro 和對應的區塊市場,以及衍生組件庫 Pro Layout 和 Pro Table。設計上也提供了非常多的規范文檔以及相關的設計資產。

        一夜之間消失,原因不明

        Ant Design

        賬號頁面消失的具體原因目前尚未公布,有網友表示或許是在從 GitHub 向 Gitee 進行遷移,也有網友表示疑似被黑客入侵導致。

        Ant Design

        具體原因我們尚未可知,但據悉 Ant Design 團隊正在積極聯系 GtiHub 官方進行協調幫助。開發者們依然可以在官網或者 Gitee 上查看及使用相關代碼:

        https://ant-design.gitee.io
        https://gitee.com/ant-design/...

        segmentfault 思否

        查看原文

        贊 5 收藏 0 評論 1

        認證與成就

        • 認證信息 SegmentFault 運營合伙人
        • 獲得 152 次點贊
        • 獲得 6 枚徽章 獲得 0 枚金徽章, 獲得 0 枚銀徽章, 獲得 6 枚銅徽章

        擅長技能
        編輯

        開源項目 & 著作
        編輯

        • SegmentFault 思否技術人訪談

          通過采訪一線技術領袖,將他們真實的成長經歷以及對技術的熱愛傳遞給更多人,幫助技術人成長。

        注冊于 2018-11-14
        個人主頁被 7.4k 人瀏覽

        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕