Blog
Blog
PHODAL
PS:文章仅为个人观点 —— 本文的内容基于我这几年在开源世界的观察得出的结论,并非调查所得到的结果。 上周,我在 GitHub 上发布了 Ledge 知识平台,我以一种“重量级”的方式来运行这个开源项目。换句话来说,以正确的方式运行起了这个项目。因为我知道怎么运作一个开源项目,加上一些外部的原因,我开始思考个人开源和组织开源的一些困境。 开始之前,让我讲个笑话和无奈: 组织开源的四大笑话是:一次性开源、按揭开源、KPI 驱动式开源、社区是什么?(分别代表了国内的几加大公司的开源做法) 不好意思,你们是对开源有什么误解吗? ## 什么是开源 事实上,我们经常混淆了两个概念,那就是开源软件和开源这个行为。 > 开源软件是源代码可以任意获取的计算机软件,任何人都能查看、修改和分发他们认为合适的代码。开源软件依托同行评审和社区生产,皆以分散、协作的方式开发。 —— 红帽官网 换句话来说,你选择一个协议,将你写的代码公开发布,这叫开源一个软件。但是,它并不叫你搞开源。开源源于开源软件,但是它现在已经成为超越软件生产的运行和工作方式。 > 开源源于开源软件生产的运行和工作方式,它是一种基于去中心化、自组织式的软件开发模式运作的工作方式。它以社区作为根基,通过开放、透明、协作几项原则开展的活动。 ## 开源不是公开代码 在那本开源的《GitHub 漫游指南》里,我一直在讲述如何在 GitHub 上开发一个 “成功” 的开源项目。因为开源不仅仅只是说源代码的开源,还包含了设计文档、产品的内容等等,还要以开源的方式来运作。以 opensource.com 对于开源方式的解释来说,需要这么五个维度: 1. 透明度。 2. 协作。 3. 尽早发布、持续发布。快速建立原型,发布第一个版本,并且不断地快速地迭代。 4. 精英制度。根据提出的最佳方案做决定的方式 5. 社区。形成社区,提升社区参与度,转化为社区目标。 也因此,如果只是公开源码的话,那是走到开源的第一步,刚来到开源的起跑线上而已,还没参与到这个游戏中去。 ## 一个开源项目是一个产品 作为一个资深的开源运动参与者,我有一个这样的体会:运营一个开源项目,就好像创业一样。我们需要采用《黑客与画家》作者 Paul Graham 所说的创业公式: 1. 搭建原型 2. 上线运营 3. 收集反馈 4. 调整产品 5. 成长壮大 所以,开源就像是一场小型创业,需要进行竞品分析,需要制定合理的策略。当然了,如果你的东西绝无仅有,那就无需如此。而除了分析市场,针对于开发人员,还要考虑: 1. 作为投资人(技术投资),他们能获得什么?提升技术?找个好工作? 2. 作为潜在的用户,从哪里知道这个项目? 3. 作为贡献者,如何提供不同级别的贡献计划? 4. …… 你并不一定非得去考虑这些问题,只要在持续完善的过程中,这些问题的答案就会浮现出来。只是呢,在你开始之前想好,可能会事半功倍。 ## 开源的重点在于生态建设 对于个人来说,开源的目的可能是找个好工作、为以后找个好工作……;对于一家组织来说,他们考虑开源可能有多种多样的目的: 1. 降低开发、维护成本。由社区来帮助寻找 bug,提出一些观点。 2. 技术影响力招聘 3. 建立技术壁垒。 4. 营造生态。 5. …… 一个好的开源作品,需要连接到上下游,即影响开发者,又影响使用者。慢慢地,它个作品就会成为一个影响行业的存在。尽管会不断有其它的项目冒出来,但是由于稳固的生态建设,将巩固组织在该领域的影响力。 ## 组织需要制定开源策略 从开头的大部分四大难题:一次性开源、按揭开源、KPI 驱动式开源、社区是什么?。我们就会发现:国内大公司的开源策略都是错的。 他们可能,今年发布 Phodal UI,明后发布 Phodal Compiler,后年发布 Phodal OS。然后,中间靠各种公关稿,完成在社区的宣传。 应该是这样的,今年发布 Phodal UI 1.0,年中发布 Phodal UI 2.0,明年发布 Phodal UI 3.0 和 Phodal Compiler 1.0,明年年中 Phodal UI 4.0 + Phodal Compiler 2.0。过程中,需要依赖于布道师来进行闭环: 1. 维护开发者关系 2. 在社区进行宣传 3. 对社区进行支持、收集社区反馈 4. 建立连接内部的通道 5. 促进内部进行改进。 这些组织需要建立一个具备可持续性的开源策略: 1. 明确其带来的业务价值(如人才引进 、生态等) 2. 专职的开发人员进行开源支持 3. 开放式的开源团队组织结构 4. 合理、适当地长期 KPI 考核机制 5. 政策和流程支持。如专项鼓励奖金 6. 明确地专利和知识产权机制 ## 开源到开放式组织 领导力变化,当我们在组织中开发一个软件应用时是以职权影响力为核心构建的;而开源方式,则是以非职权影响力构建的。 社区的每个人都可以提出自己的意见,你可以 say No,但是每个人都可以提出意见。就这一点来说,对于大部分的国内公司来说是一种挑战,大部分的领导希望听到统一的声音 —— 论组织内多样性的重要。 简单来说,大家想来就可以来,想走就可以走。所以,开源的一个难点就在于:如何吸引到人来参与开发。 尽管大部分项目都是围绕个人、团队的中心化开放式组织,如 linus 之于 Linux。但是,开源还可能变成一个中心化的组织,如 Node.js 的 IO.js 出走事件。根据开源协议,人们可以很容易派生出一个新的项目。 ## 结论 开源,就是生态。

> 文档代码化,将文档以类代码的领域特定语言的方式编写,并借鉴软件开发的方式(如源码管理、部署)进行管理。它可以借助于特定的工具进行编辑、预览、查看,又或者是通过专属的系统部署到服务器上。面向非技术人员的文档代码化的一种常见架构模式是:编辑-发布-开发分离』,

最近,我在设计、开发、维护一个基于『文档代码化』思想的平台。因为丰富的 markdown 经验和文档化系统的设计经验,我在这个系统中实施了很多过去的一些想法。系统工作得很好,但是代码却显得一片混乱。而,我突然觉得这是一件好事。 ## 最佳实践很浪费时间 对于敏捷开发来说,我只采纳了持续集成和持续部署的思想,即提交代码便发布到 GitHub Pages。但是,这也浪费了我很多的时间,而且我觉得没有必要,因为我已经有一个本地可以部署的脚本。我只需要在本地运行一下 ``deploy``,那么就会在本地构建,并部署到服务器上。然而,为了最佳实践的理念,我还是花了半天的时间,研究了一下 GitHub Action,然后让它实现自动部署。 系统的 UI 采用的于 Angular 框架,因为我懒得搭建脚手架,而且我还有一些先前的代码可以复用。所以,我 copy / paste 大量的代码,这些代码大部分都是没有测试覆盖的。是的,你很少看到我的开源项目是没有测试覆盖的 —— 毕竟写单元测试也是要花时间的。过去,我们统计过一个相关的数据,在我们日常的开发中,我们差不多有 1/5 的时间花在了单元测试。所以,一周下来,我差不多一天的时间在写测试这件事上。 ## 一个问题,三种方法实现 如开头所说,整个系统的核心是一个基于 markdown 的多功能渲染引擎。这部分的组件可以让你用 markdown: 1. 画出条形图、雷达图、思维导图 2. 画出甘特图 3. 画出特定的四象限 4. 调用 Web Components 5. …… 而为了实现这个功能,一共有三套不同的机制,当然了对于写 markdown 的人来说,它们是无感知的。这三种方法分别有: 1. 创建占位符,渲染完成后,替换占位符 2. 直接生成最后要渲染的 HTML 3. 生成一个 ID,在渲染的过程中,根据 ID 替换元素。 所以,整个过程就相当于,是解决一个问题有三个方法,然后我都用了这三种方法。 ## 起初,我只想创建个原型 起初,我只是想创建一个简单的系统,它只是一个简单的原型。而后,随着越来越多想法的产出,我创建了一个足够复杂的系统。所以,我起初设计的一系列要素都失效了。 我还有一堆糟糕的 SCSS 要管理,因为第一个版本设计的 CSS 体系,无法适用于新的架构。整个系统围绕在 markdown render 上,而这个 render 有大量的样式,就好像早期的多核 CPU 架构,只有一个 CPU 在工作,毕竟有大量的工作。 随着系统越来越复杂,我开始需要一个文档系统来管理这个文档系统,以便告诉我自己:系统里有什么功能? ## 一片混乱,真的很爽 是的,现在,现在虽然看上去界面很美观,功能也很强大。就好多是我们看到的其它软件系统一样,但是内部真的是一片混乱。如果你习惯了这样的系统的代码,那么你可能觉得这不是一个问题。 但是,我习惯了在项目中引用各种最佳实践。看了这样一个系统,觉得非常的爽 —— 大部分系统就是这样的:**同样的时间压力之下,我做得也就那样**。虽然,我的手速可能比大部分人还快,实现的功能可能更多。但是,这些都是无关紧要的。 如果没有充足的时间改善,我们的系统都变得一片混乱。没有符合未来变化的设计,更何况你可能没有时间设计。 ## 好了,是时候重构 所以,经历了一系列的过程之后,我决定挑个合适的时候重建这个系统。 1. 开始重写之前的 markdown converter。 2. 将 markdown render 所有相关的组件提取为框架。 3. 重写系统。 于是,我们就出现了第二个系统,它设计良好。它的灵感都来自于我们在做第一个系统中的感受。如果没有来自于我们第一个系统的经验,我们无法设计出第二个系统。 ## 经验:快速验证概念,创造业务价值 事实上,我们在市面上看到的大部分系统,都是以如此的方式演进的: - 第一个系统,赚了钱,创造了价值,但是缺少各种最佳实践,生存周期短。 - 第二个系统,设计良好,包含了各种实践,生存周期变长,但是慢慢变得臃肿 而我们的第二个系统很快将变成一个臃肿而缓慢的系统。 > 『第三个系统』由那些为 『第二个系统』 所累的人们创建。 —— 《Linux/Unix设计思想》 没有新系统,哪来的 KPI? 感谢您的支持,祝您生活愉快!

过去的几年里,我一直在打造各式各样的编程相关的工具。这些工具有的是用于指导软件开发工作,有的是用来进行编程学习,还有的纯粹是为了提升技术而写的。在我写了越来越多的工具,接触了越来越多的工具思路之后。 我便想写一篇文章,用于记录一下过程中发生的一些变化。

过去的一个月里,因为种种不可告人的原因,我开始建设一个 DevOps 知识平台。因为,现有的笔记系统都太 TM 难用了。所以,我要写一个,我要造一个轮子。在这一个轮子里,它可以通过 markdown 编写内容,并渲染出各种绚丽的效果。哦,不对,这部分的内容偏题了。 在这个知识平台里, 它包含了这么一些内容: - **DevOps 工具元素周期表**。帮助您进行数字化时代的 DevOps 工具选型。 - **DevOps 设计工具**。帮助您设计组织内的 DevOps 流程,涵盖了流程、人、工具、制品等等。 - **案例学习**。从社区的知识库中,我们总结了传统企业走向 DevOps 的经验,并浓缩到易于使用的内容和材料中。 - **最佳实践**。我们从海量的 DevOps 内容中,提炼出了一系列的最佳实践,以更好地帮助企业进行 DevOps 实践。 - **模式与原则**。基于我们的实践,我们提炼了位于它背后的模式与原则,帮助个人和组织更好地了解 DevOps 文化。 - **操作手册**。只凭实践与原则,无法让中小型 IT 团队进行 DevOps 转型,所以我们准备了详实的操作手册,以帮助您一步步前进。 - **度量**。KPI - 度量、度量 - KPI、KPI - 度量,帮助您更好地度量 DevOps 转型情况。 - **报告**。我们尝试从丰富地 DevOps 报告中,提炼出有用的实践和工具。 - **Mobile DevOps**。我们相信移动应用的 DevOps 改进,才是大多数公司的挑战。 - **工具**。工具,工具,工具是最好的生产力,工具比人的记忆力更加可靠。 起先,我是想做一些 DevOps 工具,比如说适合于中国国情的『DevOps 元素周期表』。顺带一说,这个工具不是我首创的,我只是用更好的架构实现了一遍。。如此一来,对于大部分开发人员来说,它们就可以从这个表中,组合出适合于自己组织的分子(毕竟周期表上都是原子)。几天之后,我就有了这个工具,根据整个研发体系的每一个过程,你可以从中挑选出适合你的要素。 为了凑满上面的元素,我不得不找一个又一个大公司的案例,看看他们到底是用什么技术栈。所以,我七拼八凑得差不多了,顺便一想,既然我有这么多大公司的案例,为什么不抽象一下这些案例呢。 于是,我们从互联网的各个地方(来源见内容中标明的出处),帮你抽取了各大公司的案例: - 腾讯 - 小米 - 招商银行 - 美团 - …… 在这些案例,背后往往包含、隐藏了各种各样的价值取向。所以,进一步地,我想去提取这些模式,所以就包含了: - 流畅度模式 - 度量体系设计 - 学习型组织构建 - …… 画完这些大包之后,随后,我们就可以进入 DevOps 的设计和实施阶段。我们要找到那些最好的实践:编程、团队、文化、能力、测试等等。 太多,不写。 当然了,为了在组织中实施 DevOps,我们还需要一本操作手册,来帮助你一步步构建 DevOps 体系。 从度量,到实践,到工程化,再到流程打通,顺势而来,一步下实践。 光有手册是不行的,我们还把各种各样的工具做了上去,除了工具的名称,还包含: - 工具的准备事项 - 工具的操作步骤 - 工具的示例 - 该工具的在线工具使用 还有更多的功能在开发中。 也欢迎加入我们的开发队伍,更多的案例将帮助每个人更好地成长。 GitHub:https://github.com/phodal/ledge/ 在线使用:https://devops.phodal.com/

软件开发是一项复杂的集体活动,它涉及到一系列的行为和艺术,如项目管理、流程管控、知识转化、程序员心理学(狗头)等。从个体出发时,这些都是一些无关紧要的因素。作为一个 “螺丝钉”,我们所关心的是:如何去解决问题?当然了,我们把组织视为一个个体时,我们也只关心:如何去解决问题?

PS:本文所针对的场景,都是复杂业务场景下的 Web 应用。简单的 Web 应用不适合复杂的架构模式,它为带来巨大的成本。 在接触了领域驱动设计的概念,其中关于核心域的想法让人颇为激动。而在微服务架构中,核心域是一个或者多个服务的域,而位于核心域的核心则是领域模型。简单地来说,对于一个系统来说,它的核心的核心的 ”核心“ 就是:领域模型。 过去,当我们谈及领域模型的时候,我们往往将其置于 Web 后端的领域。当我 GET 了一些基本的理念之后,便尝试整合到前端架构中。去年,我开始了 在项目上的第一次尝试,并在 GitHub 上创建了 [clean-frontend](https://github.com/phodal/clean-frontend) 项目,它以 Angular 作为示例,介绍了如何开发一个整洁前端架构的前端应用。 ## 引子 1:模型一致化 我习惯于在公司的项目中引入成熟的框架,从几年前的 Angular.js 到近几年的 Angular,完整的框架能为企业带来更少的维护成本,从而降低软件开发成本。几年前,我们引入了 Angular 之后,便开始大量地 TypeScript 项目实践。作为一个支持静态类型的语言,它非常适合于开发大型应用、企业应用,在这一点上你可以从 React、Vue 等框架建议使用 TypeScript 看到一种趋势。 采纳了 TypeScript 之后,当前端从后端获取到数据之后,那么它需要将 JSON 数据映射到对应的 interface。如此一来,前端的模型也就有了对应的领域模型。而这个数据模型和后端的数据模型应该是完成一致的,但是实际上,它往往是落后的。后端的 API 发生的变更之后,才需要前端同步去修改模型。 而当我作为一个前端的 Tech Lead 来考虑这个问题的时候,我首先想到的是:读取后端的 Java 代码,然后生成对应的前端模型。在没有造 Chapi 的轮子之前,通过 TypeScript Compiler 转换获得对应的类型,是我觉得比较靠谱的方案。在有了 Chapi 之后,我觉得它们都是小问题了。 不过,然后我一想这样做的意义并不大,还不如:套用后端的契约测试,造一个前端的自动化契约测试,即:[mest](http://github.com/phodal/mest)。它通过 API 返回数据和 TypeScript 的 Interface 来完成对于契约的测试。一个简单的测试数据如下: ```csv url,interface https://phodal.github.io/mest-test/error.json,mock/IError.ts ``` 通过 HTTP 请求获取对应的测试数据,再将其与本地的模型进行比较。 不过呢,我们还要使用 JavaScript 语言重写部分 Java 代码,那么我们为什么不要 JavaScript / TypeScript 重写一切呢? ## 引子 2:JavaScript / TypeScript 重写一切 遗憾的是,使用 JavaScript 编写后端应用(BFF、胶水层除外),在大部分的大公司是比较难的。内部的生态链和运维影响了技术决策,使用 C++ 不香吗,使用 JavaScript 会存在无人在背后支持。 ### 纯 Node.js 后端应用 在过去的几年里,我建议:小公司的后端应用**不要**使用 JavaScript / TypeScript 编写,因为运维、监控、APM 等生态尚不够完善。小公司应该优先投资于业务领域,在基础设施的投入见效比较短,除非能控制好开发人员的流动性。 不过,不管怎样,已经有大量的小公司因为人力成本的原因,已经使用上了 Node.js 来开发后端应用。 ### Serverless 应用 而随着 JavaScript 系生态的完善,基础设施已经不是会成为小公司的负担,我便觉得这是一个好的时机。不过,我指的是采用 Serverlesss 架构,而非自建 JavaScript 系生态。Serverless 不仅帮助小企业解决了基础设施的问题,还能为小企业降低软件运维成本。一旦企业做大之后,也可以自建采用 OpenFaas 等开源方案解决 Serverless 的供应商锁定问题。 不论是 Node.js 后端应用还是 Serverless 应用,因为使用的是同一种语言,我们可以轻松地在前后端之间共享代码,可以是 Git submodule、NPM 包、远程等等的方式。 ## 引子 3:纯编译到 JavaScript 市面上有各种各样支持 compile to js 的语言、框架,诸如于 Kotlin.js、Scala.js、Python.js 等等。 对于小前端的应用来说,这种架构非常的不错。它相当于是渐进式的系统架构方案,当前采用了主流的前端框架,而非传统的后端渲染机制,并统一了技术栈,降低了组织内部的学习成本。不过,它带来额外的调试因素,毕竟每多一层封装,系统的复杂度就需要 * 2。 而为了让框架的使用者支持不同的框架 React、Angular、Vue,这个框架还需要提供这些框架的 bind 或者是 wrapper,以提升框架使用者的幸福感。 但是,我们的挑战依然是复杂的前端应用,以及它难以消除交互的复杂度。 ## 引子 4:共享领域模型/模式库 开始之前,我不得不强调一,领域模型是一种包含数据和行为的抽血模型。 编译到 JavaScript 是一种轻前端的方案,而对于重前端的项目来说,它们完成可以采用一种新的模式:共享领域模型。即将领域模型作为模式库,提供给前后端一起使用。即,我们只需要编译所需要的部分。 这在我使用了 Kotlin 的多平台技术(multiplatform)重写了 Chapi 的 domain 层之后,我意识到了这是一个非常迷人的方案。我即使用在前端代码中使用 Chapi 的领域模型,我还需要在后端的代码中使用这套模型。原先,我需要手动翻译一行行代码,现在我并不需要这样的一个步骤。只需要在 pom.xml、build.gradle 或者是 package.json 中引入依赖及其对应的版本即可。 在引入了这部分的代码之后,我们再关注于 UI 交互部分即可。 ## 引子 5:领域模型编译到 WASM 考虑到并非所有的语言都能支持 compile to JavaScript,一种颇为有效的方式就是使用 WASM。 > WebAssembly 或称 wasm 是一个实验性的低端编程语言,应用于浏览器内的客户端。WebAssembly 将让开发者能运用自己熟悉的编程语言编译,再藉虚拟机引擎在浏览器内运行。 —— [维基百科](https://zh.wikipedia.org/zh-cn/WebAssembly) 我尝试将使用 Go 编写的 Coca 编译成 WASM,但是遇到一系列的问题(我已经忘了),体积似乎是个问题,所以我尝试使用 Rust 去构建另外一种可能性。Rust 官方提供了 Rust Webpack Template,因此我可以将其集成到我现有的前端应用中。只是呢,似乎还没有人会使用 Rust 去编写后端应用。 但是 WASM 提供了一种更友好的方式,即我们不需要重写现有的代码,而是只需要添加一些代码,便可以将现有的后端模型代码提到给前端使用。并且,与混淆后的 JavaScript 相比,它看上去更加安全 —— 学习成本更高一些。 ## 引子 6:ComponentLess 在研究 Serverless 和微前端的期间,我突然有了一点想法,我对于客户端领域的 Serverless 式架构有了一些基本的构想,叫:ComponentLess。尽管有了一些基础的理念,但是还缺乏一个真实可用的 Demo,所以我并没有定义出什么是 ComponentLess。 起先,我以为无代码编程是一个 ComponentLess 方向,但是一研究发现并不是。无代码编程倾向于可视化编程,而 ComponentLess 倾向于使用 DSL 编程。就这一点来说,我便偏向于使用 Web Components + WAM 技术来构建新的前端架构。 ### ComponentLess 从单体应用转向微服务架构的一大特质是,组件(非单指 UI 组件,可以视为服务)由函数调用转向了 HTTP 调用。而 Serverless 进一步地将微服务的服务级别 HTTP 调用,细化为函数级别的 HTTP 调用。 对于前端领域来说,也是如此。微前端将单体应用拆分一个个的独立运行前端应用,我们可以随意地组合这些应用。进一步地,结合诸如于 Web Component 这样的组件级方案,便可以将拆分细分为到 UI 组件的粒度。我们可以使用 (HTML Imports 已遗弃)script 标签从远程导入: ``` ``` 因此,在未来,不论是前端开发人员,还是开发人员,都可以通过集成组件的方式来开发应用。也就是说,我们只需要关注于编写核心业务代码即可,剩下的部分可以通过一些特殊的方式来实现。 ### DSL 抽象化代码 ComponentLess + Serverless 是**代码即基础设施**开始的一个标志。当代码开始作为基础设施的一部分时,代码便需要以某种方式才能组合到一起。在 Serverless Framework 中,开发人员通过配置接入服务端所使用的基础信息。而 YAML 配置本身也是 DSL 的一种,缺乏灵活度,但是使用非常简单。 事实上,这是两个选择: - 配置 + 编程语言。 上手容易,迁移难 - DSL。 上手复杂,易于迁移 但是,无论如何我还是如何 DSL,它听上去有着更丰富的 KPI。至于如何抽象化基础设计代码,可以参考《[云研发:研发即代码](https://github.com/phodal/cloud-dev)》一文。 ### 模型复用 不论是 ComponentLess 还是 Serverless,它们都是由函数、UI 组件变为一个独立可运行的单元。为了与别人交互,它需要包含输入和输出。而输入和输出本身是需要一个数据模型作为支撑的,以此才能完成整个系统的稳定性。 而这个话题已经回到我们开头所讨论的内容里。 ## 前后端分离将死? 在所有的引子里, 我们已经准备了所有的论据,所以只需要: - 使用可以跨前后端的语言,构建领域模型 - 将后端服务、前端设施细化为更小的组件 - 设计 DSL 将领域模型转换到特定平台的代码 你就可以杀死前后端分离,就是这么简单。 前后端分离将死,不是现在,但是可能在五年后开始。 你说呢? ## 其它 架构,没有终点。

去年年底,在公司大佬的带领下,我们结合架构守护的需要,对代码进行了简单的建模。在过去的几个月里,我一直工作在相关的事项上,不断地优化、改进相关的模型:

说来惭愧,我并没有理由来再写这样一篇文章,因为答案大家都知道,只是呢,大家仍然都好奇 —— 或许大家是想找个合适的借口,以自我安慰;而不是成为努力的一个方向。

跨平台不是一个新的话题,它已经被讨论了几十年了。在最近的一些尝试,让我对跨平台有了一些新的想法。在想法真正落地之前,我梳理了一下不同跨平台方案的一些特征,便有了它的几种模式。

Feeds

RSS / Atom

最近文章

关于作者

Phodal Huang

Engineer, Consultant, Writer, Designer

ThoughtWorks 技术专家

工程师 / 咨询师 / 作家 / 设计学徒

开源深度爱好者

出版有《前端架构:从入门到微前端》、《自己动手设计物联网》、《全栈应用开发:精益实践》

联系我: h@phodal.com

微信公众号: 最新技术分享

存档

分类

标签

作者