RASIS

  • Home

  • Archives

  • About

  • Tags

  • 8-Puzzle

  • XiangQi

使用UML工具进行业务分析

Posted on 2018-04-13 | Edited on 2024-10-23

用例建模

用例模型(Use Case Model)是帮助进行需求分析的核心技术,建立用例模型能够使需求得到系统化、结构化的展示,同时在此基础上您还可以快速分析产品可创新性,制订创新用例。

接下来,通过引入一个预定酒店的实例,让您能对用例建模快速入门。预定酒店是互联网常见业务,您会看到两个处于不同时间点的预定酒店系统,通过对比这两个用例模型,您还可以了解到项目早期发现创新点的思路方法。

早期预定旅馆业务:Asg_RH

有关Asg_RH的相关文档您可以访问这里。

如下是一个早期的旅馆预定业务用例图,在这个用例图中,有Actor Client,也就是客户;有一个系统Reserve Hotel和一个外部系统Credit Card Services;有8个用例。

现代旅馆预定业务:Airbnb

严格来说,Airbnb并不是预定酒店,而是预定民宿或度假屋,这里选用Airbnb作为例子有两点考虑:(1)Airbnb包含了所有预定酒店的业务过程;(2)Airbnb的业务流程相对复杂,能够更好地展示用例建模的特性。

如下是一个Airbnb系统的用例图,在这个用例图中,标红的是与Asg_RH相比新增的用例或外部系统。

对比分析

两个用例模型分别处于不同的时间和地区。Asg_RH比较早期,主要面向澳大利亚用户,对澳大利亚的地区支持比较完整,业务功能相对比较简单;Airbnb则是一款现代全球住宿预定的解决方案,除了主体业务之外还包含了许多扩展性业务,致力于打造旅游住宿的超级APP。

在项目早期,您会发现,整个项目的功能需求一定是模糊的、不完整的,在这种情况下,要想进行创新更为困难。使用用例模型的技术,在不断迭代的过程中,可以基本正确地确定主要的业务,并且能够发现一些创新点——根据项目的总体目标,尽可能地在用例图中添加一些用例,例如Airbnb就有”添加民宿到愿望单”和”编写旅行攻略心得”两个创新用例,这与其产品最终定位密切相关。

Backlog

Backlog是敏捷开发中的技术,您可以将Backlog与用例模型相结合使用,Backlog可以视为一个袖珍的用例集合,并且附带了对工作量和重要性的估计,既可以作为用例编制的基础框架,也可以指导进度和人员的管理。

如下是Airbnb的一个Backlog,您可以从中体会其在敏捷开发中的作用。

ID Name Imp Est How to demo Notes
1 获取特定的旅馆表 100 10 打开页面,填写旅馆搜索条件,获得符合条件的旅馆列表。 搜索条件包括时间、地区等。
2 选择旅馆 100 10 在旅馆列表中点击旅馆,浏览旅馆信息,填写必要信息,确认。 旅馆信息和填写的信息见8和9。
3 提交预定请求 100 10 选择完旅馆后,浏览预定订单全貌以及房屋主联系方式,提交。 无。
4 添加旅馆到愿望单 50 5 选择一个旅馆,点击添加至愿望单按钮,检查愿望单页面中增加了一个新旅馆。 无。
5 使用信用卡支付预定订单 100 8 选择信用卡类别,填写信用卡信息,提交交易请求。 无。
6 获取推荐的旅馆表 50 15 登陆后,在主页上看到由系统推荐的旅馆列表。 推荐算法有多种选择,工作量较大,可放到后期进行。
7 排序旅馆表 80 10 选择排序和过滤方式,列表自动排序。 排序可按照地点、价格、被预定次数等等。
8 获得旅馆的详细信息 100 5 选择旅馆后,展示旅馆的完整信息,包括地址、价格、房间结构、有无空调等等。 旅馆的信息需要仔细斟酌。
9 选择预定日期 100 5 选择旅馆后,填写预定的起止日期,确认。 无。
10 联络房屋主 90 10 点击房屋主,到达房屋主个人信息页,浏览联系方式,或发送站内信。 无。
11 发布旅行心得 50 10 点击创建旅行心得按钮,编写旅行心得,点击发布按钮,检查自己的心得页面新增了一条旅行心得。 无。
12 使用支付宝支付预定订单 95 8 选择支付宝支付,转到支付宝支付页面,进行支付。 无。
13 管理房屋 100 12 进入管理房屋页面,能够添加新房屋、编辑房屋信息、删除房屋。 可以细分。
14 获得预定历史订单 90 10 点击历史订单页面,获得所有历史订单列表,点击某个订单转到订单详情页。 无。
15 收款 100 5 登记自己的收款账号,系统自动将订房客的付款打到指定账户上。 无。

业务建模

活动图(Activity Diagram)常用于描述一个具体的业务,通过活动流程图与用例模型的组合使用,您可以快速掌握一个具体业务的流程,并发现可能的子用例。

查找酒店

如下是Airbnb用例中Get a list of specified houses的活动图,

通过浏览活动图,您可以观察每一个活动是否还有可扩展的地方,例如在选择城市部分,可以考虑

  • 如何制作城市索引
  • 是否要获取用户位置
  • 是否要进行智能推荐
  • …

不断地对所有活动进行这样的思考,是发现子用例的一个重要方法。

取款

使用自动取款机取款是身边的常见业务,如下是一个中国银行ATM取款业务活动图,

淘宝退货

多泳道图是活动图的一种特殊形式,用于描述业务中不同实体之间的相互关系和协作,如下是描述淘宝网退货业务的一个多泳道图,包含客户、淘宝网、淘宝商家服务系统以及商家四个实体。

客户须要完成退款业务,淘宝网至少要实现如下系统用例:

  • 处理用户退款请求
  • 处理用户退款物流信息
  • 改变订单状态
  • 处理退款

用例编制

用例文档具有三种常见的形式,分别是

  1. Brief
  2. Casual
  3. Full

三种形式各有优缺点,用例文档形式时要根据用例复杂程度和实际情况来选择,一味的使用完整用例不见得是一件好事。

文档形式 优点 缺点
Brief 极简,编制几乎不需要成本。 信息少,只能用于描述简单用例或不重要用例。
Casual 较为简单,能够表示一定量信息。 难以清晰描述复杂用例。
Full 结构清晰,内容翔实。 编制成本高,往往要经过多个迭代才能得到相对完整的用例文档。

绘制球体

Posted on 2018-04-08 | Edited on 2024-10-23

三角形、线、点是OpenGL3提供的基本图元,其他图形,包括再平常不过的矩形,都要通过这些基本图元构建而成。球体是3D中具有代表性图形,您可以通过以下的算法绘制一个任意大小、优美的球体。

这个算法的基本思想是,“膨胀”一个正八面体。八面体是由八个正三角形构成的空间几何体,我们令八面体的中心位于球心,八面体内切于球体,那么八面体表面上的任意一点可以延伸到球面上。这样做的好处是,我们可以在正三角形的内部绘制子三角形,以让三角形作为基本单位扩散至球体表面,方便以三角形作为基本图元进行绘制。

我们起始于一个6个顶点,8个三角形的正八面体,对于面上每一个三角形,进行递归分解——依次连接三条边的中点形成小三角形。当分解的小三角形越,进行接下来扩散得到的球体效果也会越好。

References

c++ - Drawing Sphere in OpenGL without using gluSphere()? - Stack Overflow

软件团队管理基础

Posted on 2018-03-18 | Edited on 2024-10-23

1. 简述瀑布模型、增量模型、螺旋模型(含原型方法)的优缺点。

- 瀑布模型 增量模型 螺旋模型
优点
  • 降低软件开发的复杂程度,提高软件开发过程的透明性,提高软件开发过程的可管理性;
  • 推迟软件实现,强调在软件实现之前的分析和设计工作;
  • 以项目的阶段评审和文档控制为手段有效地对整个开发过程进行指导,保证了阶段之间的正确衔接,能够及时发现并纠正开发过程中存在的缺陷,使产品达到预期的质量要求。
  • 增强客户对系统的信心;
  • 降低系统失败风险;
  • 提高系统可靠性;
  • 提高系统的稳定性和可维护性。
引入了明确的风险管理,能够有效控制风险,适合于大型软件开发。
缺点
  • 强调过程活动的线性顺序;
  • 缺乏灵活性,特别是无法解决软件需求不明确的问题;
  • 风险控制能力较弱;
  • 文档驱动,文档数目过多会极大地增加系统的工作量。
  • 增量力度难以选择;
  • 确定所有的基本业务服务比较困难。
模型系统复杂,加上严密的风险管理成本较高。

2. 简述UP的三大特点,其中哪些内容体现了用户驱动的开发,哪些内容体现风险驱动的开发?

  • 迭代和增量(Iterative and incremental)

    精化、构建和产品交付阶段被分成一系列迭代过程,每个迭代过程的结果就是一个在过去结果基础上的增量。

  • 以构架为中心(Architecture-centric)

    构架是项目团队工作的中心,UP支持复数个构架模型。

  • 风险关注(Risk-focused)

    UP要求项目团队时刻关注在项目生命周期中最主要的风险,在开发过程中时刻关注这些风险的控制。

迭代和增量体现了用户驱动的开发,风险关注体现了风险驱动的开发。

3. UP四个阶段的划分准则是什么?关键的里程碑是什么?

UP的软件生命周期被分为四个顺序的阶段:初始阶段(Inception)、精化阶段(Elaboration)、构建阶段(Construction)、产品交付阶段(Transition);划分准则是不同的阶段性目标和软件生命周期。

  • 初始阶段的里程碑使一些重要的文档,包括项目构想、原始用例模型、原始业务风险评估、一个或多个原型、原始业务案例等;
  • 精化阶段的里程碑包括风险分析文档、软件体系结构基线、项目计划、可执行的进化原型、初始版本的用户手册等;
  • 构建阶段的里程碑包括可以运行的软件产品、完整的用户手册等;
  • 交付阶段的里程碑则是确定最终目标是否实现,是否应该开始下一个版本的开发周期等。

4. IT项目管理中,“工期、质量、范围/内容”三个元素中,在合同固定的条件下,为什么说”范围/内容”是项目团队易于控制的?

工期、质量可能受到外界不可控因素的影响,例如客户需求变更、软件团队人事变动等,而范围/内容是由项目团队自己制定和掌握,相对而言易于控制。

5. 为什么说,UP为企业按固定节奏生产、固定周期发布软件产品提供了依据?

UP通过迭代增量建模思想提高了风险控制能力,既严格按照时间顺序进行开发作业,同时能够管控各种可能的风险,使得开发周期得到非常有效的控制,企业能够在这种模型下精确控制开发周期,实现按固定节奏生产、固定周期发布软件产品。

6. 项目管理应用

本团队拟实现一个基于微信小程序的扫码点餐软件,以亲身体验软件工程在实际中的应用。

在整个过程中,使用看板作为团队协作的重要工具,对生产过程进行基于过程开发模型的阶段划分,看板如下所示:

软件工程部分要点

Posted on 2018-03-11 | Edited on 2024-10-23

1. 软件工程的定义

Software engineering is “(1) the application of a systematic, disciplined, quantifiable approach to the development, operation, and maintenance of software, that is, the application of engineering to software,” and “(2) the study of approaches as in (1).” – IEEE Standard 610.12

根据IEEE标准,软件工程的含义包括两个层面:

  • 系统的、秩序的、可度量地开发、操作、维护软件的方法的应用,就是软件的工程化应用。
  • 对于上述应用的研究。

2. 何为Software Crisis

Software Crisis(软件危机),是发生于上世纪六十年代的软件生产困境。随着计算机应用需求的驱动,软件生产的复杂性和成本急剧上升,而当时软件开发普遍应用“个人英雄主义”,使得大型软件的生产出现了很大的困难,出现了许多重大的问题,包括但不限于:

  • 软件开发成本日益增长,超出预算
  • 软件开发进度难以控制,实际进度与计划相比一再拖延
  • 用户需求不明确,用户对使用的软件产品不满意的现象经常发生
  • 软件产品质量不可靠
  • 软件产品的可维护程度低
  • 软件开发生产率跟不上日益增长的硬件性能和用户需求

软件工程就是为了解决软件危机所展示出来的问题,于上世纪六十年代末至七十年代初应运而生的工程学科。

3. 何为COCOMO模型

Constructive Cost Model(构造性成本模型),是由Barry Boehm于上世纪八十年大提出的一种软件成本估算方法,其目的是在软件生产之前评估软件成本,以使软件生成流程得到更加精确的管理,规避不必要的风险。

早期的构造性成本模型在准确性方面有一定的局限性,后来出现的“中级COCOMO”和“详细COCOMO”加入了更多考量因素,直至今天也是软件工程中非常热门、实用的管理控制工具。

4. 软件生命周期

Software Development Life Cycle(SDLC, 软件开发生命周期)是起源于上世纪六十年代的一种软件开发过程模型,也是软件工程中最重要的模型之一。SDLC定义了软件生产的各个阶段过程,真正使得软件生产得以系统化,也孕育了许多软件生命周期模型,是现代软件工程开发过程的指导模型。

软件生命周期一般看作有如下6个阶段(Phases):

  1. 可行性分析与初期计划

    这一阶段要分析此软件生产项目的可行可行性,完成对成本、风险和回报粗略估计,并结合自身的团队力量状况来确定是否接手此软件项目。这一阶段的任务往往由顶层人员负责,要求其具有相当丰富的软件工程经验和管理分析能力。
    ·

  2. 需求分析阶段

    分析用户的需求,确定软件系统的各项功能、性能需求和设计约束,确定对文档编制的要求,产生软件规格说明书、初步用户手册等一系列技术文档。

  3. 设计阶段

    完成软件概要设计和详细设计,即根据软件需求形成软件模块、并确定每个模块的具体输入输出、数据结构和算法实现,产生结构设计说明、详细设计说明等文档。

  4. 实现阶段

    根据模块详细设计对每一个模块进行实现,并编写用户手册、操作手册、测试计划等。

  5. 测试阶段

    全面测试软件系统,包括单元测试、集成测试、系统测试、文档测试等等,产生进度总结报告。

  6. 运行与维护阶段

    在此阶段软件已经提交给用户,在用户运行的过程中需要对其进行持续的维护,根据用户可能提出的新需求进行必要的扩充、删改和更新。

得益于软件生命周期的提出,出现了许多面向实际生产的软件生命周期模型,包括:

  • Waterfall Model(瀑布模型)
  • V Model and W Model(V模型与W模型)
  • RAD Model(快速应用开发模型)
  • Prototype Model(原型模型)
  • Incremental Model(增量模型,又叫演化模型、迭代模型)
  • Spiral Model(螺旋模型)
  • Fountain Model(喷泉模型)
  • CBSD Model(基于构件的开发模型)
  • RUP Model(Rational统一过程模型)
  • Agile Modeling and XP(敏捷开发模型与极限编程)
  • …

5. 按照SWEBok的KA划分,“系统分析与设计”课程应该关注哪些KA或知识领域?

Software Engineering Body of Knowledge(SWEBOK)是一个国际标准,用于定义软件工程主体相关的重要领域。SWEBOK V3为软件工程划分了15个Knowledge areas(KAs),包括:

  • Software requirements
  • Software design
  • Software construction
  • Software testing
  • Software maintenance
  • Software configuration management
  • Software engineering management
  • Software engineering process
  • Software engineering models and methods
  • Software quality
  • Software engineering professional practice
  • Software engineering economics
  • Computing foundations
  • Mathematical foundations
  • Engineering foundations

(2004版的SWEBOK划分了10个KAs,就不在此一一列举)

系统分析与设计主要面向SDLC的设计阶段,即根据需求合理地设计软件模块与模块实现方案,那么与之相关的KA应当有:

  • Software requirements(根据需求完成分析和设计)
  • Software design(系统分析和设计的主体部分)
  • Computing foundations(详细设计需要杰出的计算知识,包括数据结构、算法等)
  • Mathematical foundations(模块设计常用到图论,详细设计用到的算法知识等)

6. CMMI的五个级别

Capability Maturity Model Integration(CMMI, 能力成熟度模型集成),是一个过程改进方案,它给出对于软件成熟度的评估标准,用于开发团队在软件开发的过程中评估和改进软件产品。

  • Level 1 - Initial : 无序,自发生产模式。(第一级被看作是没有通过成熟度评估的“初始级”)
  • Level 2 - Managed : 已管理。
  • Level 3 - Defined : 已定义。
  • Level 4 - Quantitatively Managed : 已被量化地管理。
  • Level 5 - Optimizing : 优化中。

7. 用自己的语言阐述,何为SWEBok

关注一个计算机科学中的技术、工具、标准、理论,首先关注其用于解决什么问题,软件工程是用于解决上世纪六十年代发生的软件危机问题,软件生命周期是为了解决软件开发过程的系统化问题,等等。Software Engineering Body of Knowledge(SWEBok)作为国际标准ISO/IEC TR 19759:2005,在我看来,是为了明确和分清软件开发过程的各个技术领域。

划分技术领域的工作与软件生命周期类似,使得软件开发能够泾渭分明,分工明确,提高效率;不同于软件生命周期以时间为线索,SWEBok在这方面的工作是从静态的技术角度出发,系统化地管理了技术领域,使得软件开发在时间和空间两方面都得到了层次分明的蓝图。

8. PSP各项指标及技能需求

Personal Software Process(PSP),是针对个人开发人员的开发能力评估标准,由提出CMMI的团队所创造。PSP经过若干个版本的更迭,现在最新的PSP2.1对于个人开发者有如下指标:

  • Planning(计划)
    • Estimate(估计任务所需要的时间)
  • Development(开发)
    • Analysis(分析需求)
    • Design Specification(产生设计文档)
    • Design Review(设计复查)
    • Coding Standard(代码规范)
    • Design(设计)
    • Coding(编码)
    • Code Review(代码复查)
    • Test(测试)
  • Record Time Spent(记录花费时间)
  • Test Report(测试报告)
  • Size Measurement(工作量计算)
  • Postmortem(事后总结)
  • Process Improvement Plan(提出过程改进计划)

对于一个软件工程师,在接到任务后须按照上述PSP2.1的流程走一遍,与以往自发开发不同的是,PSP2.1中加入了较多的时间估计、复查和总结。这不仅要求软件工程师有过硬的编码能力,其阅读和撰写文档的技能、自我评估的技能以及测试技能等都十分重要。

对于每一项指标,采用计时的方式产生统计数据,记录并保存,项目完成后依照自己的统计数据进行处理,产生完整的、多元的PSP指标分析结果。

Golang Docker 简易使用

Posted on 2017-11-14 | Edited on 2024-10-23

什么是Docker?

摘要

Docker的概念类似于虚拟机,使用方式上和Git很像。一个虚拟机运行一个操作系统,在这个操作系统上可以运行各种各样的程序,主操作系统上的虚拟机充当一个虚拟硬件的角色,但是有一个问题:程序的运行往往需要很多依赖支持,例如C的运行时、python应用的python环境、java应用的JVM等等,在虚拟机环境下,这些运行环境还是要用户去手动配置的;而Docker更高一层,它充当的不是虚拟硬件,而是虚拟运行环境;只要把应用放入Docker中,运行时Docker会自动配置好所需的运行环境,用户在一个操作系统下就算不安装任何环境,只要装一个Docker,任何应用程序都可以直接跑起来。

Docker的结构

实际使用过程中,有两个关键性的对象——Image和Container。

An image is a lightweight, stand-alone, executable package that includes everything needed to run a piece of software, including the code, a runtime, libraries, environment variables, and config files.
A container is a runtime instance of an image—what the image becomes in memory when actually executed. It runs completely isolated from the host environment by default, only accessing host files and ports if configured to do so.
Containers run apps natively on the host machine’s kernel. They have better performance characteristics than virtual machines that only get virtual access to host resources through a hypervisor. Containers can get native access, each one running in a discrete process, taking no more memory than any other executable.

  • Image是一个轻量级的、独立的、可执行的包,它包含了所有软件运行所需要的组件。当然,我们在制作Image的时候并不需要手动去下载那些组件啊依赖什么的,因为那些组件在网络上也已经有现成的Image了,我们只管写代码,之后交给Docker去自动合成就好了。
  • Container是一个Image的运行实例。Image和Container的关系就好像程序和进程,把Image跑起来就是一个Container了,这个玩意儿里面包括了所有需要的运行环境,程序能毫无阻拦地在里面运行,并且这个东西是运行在主操作系统上的,相较于虚拟机来说肯定有性能上的优势。

为什么要使用Docker?

对于一个技术,我们要正确使用它就必须清除它是为了解决什么问题。在linux系统下玩过开发的应该都有体会,安装环境是多么令人头疼的一件事,每次都要去别人官网看一大堆教程,安装的时候还可能遇到这样那样的错误,在stackoverflow里又要逛好久;这倒不是什么事儿,装完了就一劳永逸了。关键是当我们需要在一个别的什么新的操作系统下运行一个程序,难道还得重复一遍之前的痛苦?一个典型的例子就是部署服务器了,在vps服务商那儿高高兴兴地买了个vps,登上去之后把自己的server一拷,发现根本跑不起来,又是缺这又是缺那的,这个时候我们终于意识到了,Docker这个技术是多么的伟大这个事实..所以说为什么现在各大云服务商都推出了容器服务,你根本就接触不到操作系统,把Docker的image给人一传,你的服务器马上就能工作了。(AWS的注册要国际银行卡啊..臣没有呀..)

Docker的使用

安装(Ubuntu 16.04 LTS)

当然了,就算是项羽也不能把自个儿拎起来;Docker本身还是得我们自己手动安装的。实话说,安装过程还是看官方教程比较好,博客的东西都不具有时效性啊。

  1. 卸载旧版Docker

    话说开天辟地之时,Docker是不分社区版和企业版的,安装方式也没那么复杂,可是后来社区版和企业版一分,安装方式也变了,还得我们自己卸载旧版本。不过第一次安装的话这一步就不用管了。

    $ sudo apt-get remove docker docker-engine docker.io
  2. 安装Docker CE

    我当然是安装社区版了;过程其实很简单,把下面一串命令麻溜地敲进去就好了。

    $ sudo apt-get update
    
    $ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
    
    $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    
    $ sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
    
    $ sudo apt-get update
    
    $ sudo apt-get install docker-ce

    这几个命令大概就是先安装依赖,然后创建一个专属的包仓库,再从这个仓库里安装(这样更新卸载啥的都很方便);最后一个命令才是真正的安装。BTW,docker的服务器要在国内访问可能有些困难,这个自己想办法解决吧。

    另外,计算机架构不同的话要改一改最后一个命令里的arch=amd64这个部分,具体等于啥,我也不懂,不过一般要改这个的人应该都不用看这个博客了..

    当然你也可以从官网下个deb,然后每次更新都重新下新的deb..

  3. 卸载Docker CE

    $ sudo apt-get purge docker-ce
    $ sudo rm -rf /var/lib/docker

    一般来说这辈子都没机会输入这两个命令。

安装(MAC OS X)

在OSX系统下,docker官方提供了简单好用的图形安装包。访问Docker-CE下载页,下载对应的MAC版安装包安装即可。

安装完成后,在状态栏中会运行Docker伴随程序,可以进行一些图形化配置,但最主要的还是在伴随程序开启的状态下才能够使用的命令行Docker程序。

简单使用

今天我们的目标是把一个Go语言的hello world服务器在docker里运行一波。

首先看一下源代码目录:

simple_http
> .git
> Dockerfile
> main.go
> README.md

除了Dockerfile,都和docker没啥关系了。Dockerfile是一个配置文件,所有必须的组建和依赖都在这里面进行配置,例如安装python依赖包之类的;除此之外,还有一些功能性的东西,比如导出端口啥的。

鉴于Go语言先天优势,它实在是没啥依赖,Dockerfile里只用写两行就行了

FROM golang:onbuild
EXPOSE 8080
  • FROM条目一般来说是必需的,官方有各种各样的语言镜像,提供了最基础的运行环境。FROM后跟着一个镜像名,一个镜像名后加个冒号表示TAG,也就是说golang是镜像名,onbuild是TAG。至于TAG是干啥的,我也不太清楚..
  • EXPOSE是导出的端口。Docker里的应用程序实际上仍然是在沙盒里运行的,即使程序里监听了8080端口,也是监听的那个沙盒的8080端口,我们要把沙盒的8080端口和主机的8080端口连在一起,才能访问。

什么?你说你用的是python?那Dockerfile可有的写了,善用官方文档和搜索引擎..

写完Dockerfile之后,就可以创建镜像了,cd到这个目录然后敲

$ sudo docker build -t xxx:yyy .
  • -t表示命名,xxx表示镜像名,yyy表示TAG

跑完之后,一个镜像就创建好了,可以敲入如下命令来查看

$ sudo docker images

创建完镜像之后,就是运行了,敲入如下命令

$ docker run --publish 8080:8080 --name test --rm xxx
  • --publish xxxx:yyyy 将主机的xxxx端口绑定到container的yyyy端口。
  • --name xxx 给我们的container起个名字(重要)。
  • --rm 在运行完成后删除镜像。
  • xxx 刚才创建的镜像名

这样我们的程序就在docker里跑起来了,而且监听了主机的8080端口,可以curl一下localhost:8080来试试效果。

附录

main.go

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

func main() {
    // A simple http server.
    //
    // This server send "Hello user" to client based on URL.
    // Example:
    //
    // localhost:8080/foo/bar
    // > Hello foo bar!
    //
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello %s!\n",
            strings.Join(strings.Split(r.URL.String(), "/")[1:], " "))
    })

    fmt.Println("Listening to port 8080.")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

参考

Docker Docs

Deploying Go servers with Docker - The Go Blog

Golang net/http 源码分析

Posted on 2017-11-13 | Edited on 2024-10-23

Go语言的内置包net提供了大量api,功能十分强大、实现非常优美,不读一读实在是有点可惜呀。

程序

首先,写一个简单的服务器。

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

func main() {
    // A simple http server.
    //
    // This server send "Hello user" to client based on URL.
    // Example:
    //
    // $ curl localhost:8080/foo/bar
    // > Hello foo bar!
    //
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello %s!\n",
              strings.Join(strings.Split(r.URL.String(), "/")[1:], " "))
    })

    fmt.Println("Listening to port 8080.")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

如注释所说,这个服务器会发一个“Hello xxx”给客户端,一个简单的hello world式程序。

代码分析

虽然这个服务器只有不到10行,但是通过代码追踪(ctrl+鼠标左键),我们可以一层一层地看到net/http包里各种各样的接口。

http.HandleFunc

http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))
-> (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
-> (mux *ServeMux) Handle(pattern string, handler Handler)
  • http包中的HandleFunc方法会给默认的ServerMux注册一个Handler,用于响应客户端发来的请求;它包含两个参数,一个是字符串类型的URL,另一个是Handler函数,这两个参数经过三层传递传递到DefaultServeMux.Handle函数中,才会被正式执行注册操作。
  • ServerMux是一个HTTP协议请求复用器,其中包含多个URL到Handler的映射,用于匹配不同的客户端请求,并执行相应的Handler;ServerMux支持近似匹配,当匹配不完全时,它会寻找最接近的匹配。
  • DefaultServerMux是包中自带的默认复用器,也就是说,开发者可以定义自己的复用器。
  • Handler是一个接口,包含一个处理函数。

这个过程涉及到两个重要结构ServeMux和muxEntry。

// type ServeMux
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool // whether any patterns contain hostnames
}
// type muxEntry
type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}
  • mu 一个读写排它锁,用于保证注册Handler过程的原子性。(操作系统知识怎么在这出现了)
  • m 映射,储存了从URL到muxEntry的映射,muxEntry中包含了处理函数Handler。
  • hosts 表示是否有某个pattern包含主机名。

http.ListenAndServe

http.ListenAndServe(addr string, handler Handler)
-> (srv *Server) ListenAndServe()
-> (srv *Server) Serve(l net.Listener)
  • http.ListenAndServe方法创建一个Server,监听TCP地址addr并使用handler来处理接收到的请求。
  • Server定义了运行一个HTTP服务器的各种参数,包括TCP地址、Handler、TLS参数、超时时间、最大头长度等等。
  • Server的ListenAndServe方法创建一个net包中的传输层TCP监听器,并调用Serve方法。Serve方法接收一个TCP监听器,通过该监听器获得连接信息,并为每一个连接创建一个线程并调用Server的Handler来响应。具体实现细节涉及网络编程。

Golang Log

Posted on 2017-10-31 | Edited on 2024-10-23

为什么我们需要Log?

Log, 意为原木、树干子,把树干子切开,上面有很多一圈一圈、密密麻麻的纹理;程序log也是一个一行一行、密密麻麻的文件,记录着程序运行过程中各种信息。

但这些信息不一定能全部用到,很多时候只是一种备考——发生了某种状况的时候才查阅,那不做log行不行呢?程序肯定不会因此而罢工,然而我们得知道,计算机程序在运行的时候依然是失控的,即使这个程序是我们一行一行敲出来的;由于计算机“感知”到的时间太快了,程序在一个须臾之间就能进行大量变化,我们人类根本无法以毫秒级来跟踪它。一个失控的程序,出错暴走的可能性是极高的,在加之我们更有许许多多失控的用户,下雨天,与这些失控的程序更配噢,出了个什么问题根本无法避免。所以我们得log,得让秉笔直书的logger忠实地记录下关键信息,出现问题时能够发现问题、解决问题、澄清事实和责任关系。一个健全的中大型程序,log当然是必不可少的。

Package log

扯远了..这是Golang学习…

Package log implements a simple logging package. It defines a type, Logger,
with methods for formatting output. It also has a predefined ‘standard’ Logger
accessible through helper functions Print[f|ln], Fatal[f|ln], and
Panic[f|ln], which are easier to use than creating a Logger manually. That
logger writes to standard error and prints the date and time of each logged
message. Every log message is output on a separate line: if the message being
printed does not end in a newline, the logger will add one. The Fatal
functions call os.Exit(1) after writing the log message. The Panic functions
call panic after writing the log message.

Golang的log包提供了一个简单的log功能——就是一个用多种格式化输出函数的Logger类,能够输出日期、时间、时区、文件名到标准输出、标准错误,并有一些预置函数用于在输出后进行一些其他操作。我们的重点在怎么定义我们自己的Logger类上。

创建 Logger

func New(out io.Writer, prefix string, flag int) *Logger

使用log包中的New函数创建一个新Logger。

第一个参数out表明log要写往何处。多亏了Golang的接口机制(在Golang语言特性总记有一些讨论),我们能很方便的指定目的地。

第二个参数prefix是每一条log的前缀,比如我们可以在错误记录前加上前缀“Error:”。

第三个参数flag定义了每一条log的形式,说白了就是加上一些日期啊、时间啊、文件名啊之类的信息,用log包里的常量定义。

E.g.

logFile, _ := os.Open("myLogFile")
myLogger := log.New(logFile, "[INFO]", log.Ldate|log.Ltime)

创建一个Logger,输出到文件myLogFile中,每一条记录规定加上前缀INFO,以及日期和时间。

使用 Logger

Logger创建完了,我们得记录啊。记录的时候用Logger自带的方法就行了,最简单的例如

func (l *Logger) Println(v ...interface{})

复杂点的,像

func (l *Logger) Fatalln(v ...interface{})

Fatal执行完之后会直接异常退出程序(执行os.Exit(1))

还有什么Panicln,会在执行之后执行panic()(发生了很恐怖的错误,吓得我赶紧把程序关了)

以及一些get和set函数就没了,相当轻量级啊,不过能满足需要就是好东西。

实际使用一下:

E.g.

myLogger.Println("[r0beRT] Login")

运行之后看起来就像这个样子

[INFO] 2017/10/31 15:11:39 [r0beRT] Login

log包简单易用,但是我们得自己定义我们的输出,要定义的有用而又优雅,没有多余信息且又面面俱到,输出结果还符合国际一般规范(不成文),才是大头啊,还是得自己去多看多用,积累经验呀。

参考

log - The Go Programming Language

Golang 语言特性总记

Posted on 2017-10-28 | Edited on 2024-10-23
  • Slice
  • Closure
  • Interface
  • …


看到了就会写上,不定更新。

Slice

在Gotour里,slice是这样解释的:

An array has a fixed size. A slice, on the other hand, is a
dynamically-sized, flexible view into the elements of an array. In
practice, slices are much more common than arrays.

简单翻译一下,slice就是一个动态数组,大家平时一般都用它,而不是用定长的数组。

先简单看一下slice的使用。

1
2
3
4
5
6
7
var s []int  // s is a nil slice

primesArr := [6]int{2, 3, 5, 7, 11, 13} // primesArr is an array
primesSlice := []int{2, 3, 5, 7, 11, 13} // primeSlice is a slice

primesSlice = primesSlice[0:2] // {2, 3}
primesSlice = primesSlice[2:4] // {5, 7}

然而,Gotour后还提到了,【slice是数组的引用】,那么这就是另外一个故事了。在Golang中,array和slice都有两个固有的属性length和capacity,array中这两者是恒等的,而slice中则不然。

1
2
3
4
5
6
7
8
9
var a [5]int
var s []int
var ss = make([]int, 5)
fmt.Println(len(a), cap(a), len(s), cap(s), len(ss), cap(ss))
// 5 5 0 0 5 5
fmt.Println(len(ss[0:1]), cap(ss[0:1]))
// 1 5
fmt.Println(len(ss[2:3]), cap(ss[2:3]))
// 1 3

所以对于slice正确的理解,是将其看作一个数组的引用,而不是看作一个所谓的【动态数组】。

  • 用数组初始化slice的时候,可以看作是引用了内存里的一个无名数组。

  • capacity记录的是这个slice所引用的数组的长度,而length记录的是当前引用过来的元素个数。

  • slice可进行随意的切分,但是从左边切是不可恢复的,从右边切是可恢复的,同时一旦这个slice的某个元素被更改,那么由这个slice切出来的所有slice中这个元素都将被更改。

  • 直接创建的slice空变量是没有容纳能力的,要使用make方法;若要进行深复制,也要使用make方法来创建一个新的slice。

Closure

闭包可能是学习Golang的第一个难题,但是闭包并不是Golang专属的特性,在很多其他语言中也有闭包,最典型的——Javascript.

闭包的问题难在难以界定——闭包到底是什么?能不能有一言可以蔽之?在Gotour中对闭包的解释是

A closure is a function value that references variables from outside its body.
The function may access and assign to the referenced variables; in the sense
the function is “bound” to the variables.

简而言之,就是在函数内可以保持一个函数外的局部变量的值,且不需要这个值进行任何形式的传递。我认为这段话基本上把闭包最重要的特点描述出来了——利用函数对局部变量作用域进行灵活操作。

Gotour里给的例子是一个简单的返回函数闭包,实际上闭包的用途实际上远不止于此,典型的例子如Javascript中对于循环添加事件的处理,但是其中最核心的想法是不变的,就是将局部变量保持在一个【看似】无法访问它的作用域中。

但是实际上,编成的时候大部分用到闭包的情况都是靠经验判断的,我们平时应该多多积累,在我们编程卡住的时候,不妨想想,这地方是否能用一用闭包?

Interface

Interface可以说是Golang里最最最重要的特点了,在Golang里实现【面向对象】特点全靠它,但它同时也是一个较难理解的概念。

让我们先来看Gotour是怎么说的

An interface type is defined as a set of method signatures.
A value of interface type can hold any value that implements those methods.

这两句简短的话道出了Interface大量的奥秘。首先,interface是一个方法签名集,即interface可以看作是一个集合,这个集合的元素是函数(函数签名);其次,interface又可以作为任何实现了它方法的值的变量,这句话比较难理解,让我们看几个例子。

  1. 首先是我们非常熟悉的fmt.Println()函数,可以看到,官方文档中,这个函数接受的参数是一系列的【空接口】。

    1
    func Println(a ...interface{}) (n int, err error)

    回想一下我们使用这个函数的场景,任何类型的变量都可以作为参数传入这个函数中,没错,就是因为interface{}是一个函数签名集为空的接口,既然它没有任何方法需要实现,那么根据第二句话,任何类型的变量实际上都是interface{}类型,或者说都可以转化为interface{}类型。在很多处理未知类型的情况下(模板编程),Golang提供了interface{}给我们使用。

  2. 文件读写

    1
    2
    3
    4
    file, _ := os.OpenFile(somePath, os.O_RDONLY, os.ModePerm)
    msg := make([]byte, 0)
    file.Read(msg)
    fmt.Println(string(msg))

    上面是一个简单的读文件过程,os.OpenFile返回一个os.File结构体,而调用的os.File结构体的Read方法是接口io.Reader里的方法,也就是说os.File实现了接口io.Reader.但是这并不足以体现接口在这里的作用,我们再来看下一段代码

    1
    2
    3
    4
    5
    6
    7
    file, _ := os.OpenFile(somePath, os.O_RDONLY, os.ModePerm)
    jsonDecoder := json.NewDecoder(file)
    for jsonDecoder.More() {
    var obj SomeStruct
    jsonDecoder.Decode(\&obj)
    fmt.Println(obj)
    }

    上面是一个读取json文件的例子,注意到json包中的NewDecoder方法返回一个JSON解码器,能够将JSON文件中的条目写入一个结构体中,而这个方法接受的参数类型并不是os.File,而是io.Reader,也就是说任何实现了这个接口的结构体都可以被当作参数传入,因为只要实现了io.Reader的方法就【足够】成为一个JSON解码器了。这样一来,无论是strings.Reader还是os.File,甚至是你自己实现的自定义结构体,都可以成为JSON解码器,一定程度上说,这就是体现了面向对象里的继承和多态的思想,大大提高了开发效率。

接口,是一个函数签名的集合,更是也是一个可自定义的抽象类型,正是它的存在使得在Golang里应用面向对象的设计思想成为可能。

参考

GoTour

123
r0beRT

r0beRT

28 posts
7 tags
GitHub E-Mail
© 2018 – 2024 r0beRT
Powered by Hexo v3.9.0
|
Theme – NexT.Gemini v6.4.2