Python 几种常见的测试框架

thbcm阅读(201)

测试的常用规则

  1. 一个测试单元必须关注一个很小的功能函数,证明它是正确的;
  2. 每个测试单元必须是完全独立的,必须能单独运行。这样意味着每一个测试方法必须重新加载数据,执行完毕后做一些清理工作。通常通过setUp()和setDown()方法处理;
  3. 编写执行快速的测试代码。在某些情况下,测试需要加载复杂的数据结构,而且每次执行的时候都要重新加载,这个时候测试执行会很慢。因此,在这种情况下,可以将这种测试放置一个后台的任务中。
  4. 采用测试工具并且学着怎么使用它。
  5. 在编写代码前执行完整的测试,而且在编写代码后再重新执行一次。这样能保证你后来编写的代码不会破坏任何事情;
  6. 在提交代码前执行完整的测试;
  7. 如果在开发期间被打断了工作,写一个打断的单元测试,关于你下一步将要开发的。当你回来工作时,你能知道上一步开发到的指针;
  8. 单元测试函数使用长的而且具有描述性的名字。在正式执行代码中,可能使用square()或sqr()取名,但是在测试函数中,你必须取像test_square_of_number_2()、test_square_negativer_number()这些名字,这些名字描述更加清楚;
  9. 测试代码必须具有可读性;
  10. 单元测试对新进的开发人员来说是工作指南。

单元测试的目的是对一个模块、一个函数或者一个类来进行正确性检验,如果单元测试通过,说明我们测试的对象能够正常工作。如果单元测试不通过,要么测试对象有 bug,要么测试条件输入不正确。下面小编为大家介绍 Python 的几种测试框架。

推荐好课:Python 自动化管理Python 自动化办公

1. unittest

unittest 和 JUnit类似,可以说是python的标准单元测试框架,所以有时也被人称为 PyUnit。它使用起来和xUnit 家族其他成员类似。 用的人也比较多。兼容 python2 以及python3 。

个人比较喜欢用这个,主要之前用过JUnit,用这个上手就很快。而且属于python自动集成,不用额外的安装包,感觉是该有的都有了,用着方便。

示例:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):

        self.assertEqual(‘foo’.upper(), ‘FOO’)

    def test_isupper(self):

        self.assertTrue(‘FOO’.isupper())

        self.assertFalse(‘Foo’.isupper())

    def test_split(self):

        s = ‘hello world’

        self.assertEqual(s.split(), [‘hello’, ‘world’])

        # check that s.split fails when the separator is not a string

        with self.assertRaises(TypeError):

            s.split(2)

if __name__ == ‘__main__’:

    unittest.main()

2. unittest2

unittest2 可以说是一个针对 unittest 测试框架新特性的补丁。它很大程度上和 unittest 都类似。然后还添加了一些 unittest 没有的方法。

3. pytest

参考文档:http://pytest.org/latest/

看了一下,pytest文档还是蛮详细的。比较关注的一点是,pytest 直接可以通过 @pytest.mark.parametrize 进行参数化,而unittest 则需要借助DDT。

示例:

def inc(x):

    return x + 1

def test_answer():

    assert inc(3) == 5

执行如下:

$ pytest

======= test session starts ========

platform linux — Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y

rootdir: $REGENDOC_TMPDIR, inifile:

collected 1 item

test_sample.py F

======= FAILURES ========

_______ test_answer ________

    def test_answer():

>       assert inc(3) == 5

E       assert 4 == 5

E        +  where 4 = inc(3)

test_sample.py:5: AssertionError

======= 1 failed in 0.12 seconds ========

4. nose

nose 扩展了 unittest,从而使得测试更容易。

一般可以用 unittest 方式写用例,写完之后用 nose 来执行。nose 的测试收集方式还是很方便的。

 还有一个特定就是,nose 可以采用  @with_setup() 来定义方法的 setup 和 teardown。

示例:

def setup_func():

    “set up test fixtures”

def teardown_func():

    “tear down test fixtures”

@with_setup(setup_func, teardown_func)

def test():

    “test …”

5. doctest

doctest 模块会搜索那些看起来像交互式会话的 Python 代码片段,然后尝试执行并验证结果。

doctest 中,如果要写测试用例,只需要在写在以 ”’ ”’包围的文档注释即可,也就是可以被__doc__这个属性引用到的地方。这点比较特别,跟其他单元测试框架都不一样。但是我觉得这样的话就注定了doctest不适合大型测试,因为做不到代码和测试的分离。

import doctest

“””

This is the “example” module.

The example module supplies one function, factorial().  For example,

>>> factorial(5)

120

“””

def factorial(n):

    “””Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]

    [1, 1, 2, 6, 24, 120]

    >>> factorial(30)

    265252859812191058636308480000000

    >>> factorial(-1)

    Traceback (most recent call last):

        …

    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:

    >>> factorial(30.1)

    Traceback (most recent call last):

        …

    ValueError: n must be exact integer

    >>> factorial(30.0)

    265252859812191058636308480000000

    It must also not be ridiculously large:

    >>> factorial(1e100)

    Traceback (most recent call last):

        …

    OverflowError: n too large

    “””

    import math

    if not n >= 0:

        raise ValueError(“n must be >= 0”)

    if math.floor(n) != n:

        raise ValueError(“n must be exact integer”)

    if n+1 == n:  # catch a value like 1e300

        raise OverflowError(“n too large”)

    result = 1

    factor = 2

    while factor <= n:

        result *= factor

        factor += 1

    return result

if __name__ == “__main__”:

    doctest.testmod(verbose=True)

verbose 参数用于控制是否输出详细信息,默认为 False ,如果不写,那么运行时不会输出任何东西,除非测试 fail。

输出如下:

Trying:

    [factorial(n) for n in range(6)]

Expecting:

    [1, 1, 2, 6, 24, 120]

ok

Trying:

    factorial(30)

Expecting:

    265252859812191058636308480000000

ok

Trying:

    factorial(-1)

Expecting:

    Traceback (most recent call last):

        …

    ValueError: n must be >= 0

ok

Trying:

    factorial(30.1)

Expecting:

    Traceback (most recent call last):

        …

    ValueError: n must be exact integer

ok

Trying:

    factorial(30.0)

Expecting:

    265252859812191058636308480000000

ok

Trying:

    factorial(1e100)

Expecting:

    Traceback (most recent call last):

        …

    OverflowError: n too large

ok

1 items had no tests:

    __main__

1 items passed all tests:

   6 tests in __main__.factorial

6 tests in 2 items.

6 passed and 0 failed.

Test passed.

如何将Git的界面语言设置为中文?

thbcm阅读(220)

我们从官网下载安装好 Git 客户端后,打开软件默认的是英文,那如果我们想要设置成我们熟悉的中文,那该怎么设置呢?随W3Cschool编程狮一起看看吧~

以下操作环境为 Windows10 64位

1.从开开始菜单或桌面启动 Git Bash(or桌面或任意文件夹内单击鼠标右键,选择Git Bash Here)如下图:

2.进入 Git 软件的 UI 界面中,如下图:

3.在该窗口中任意位置单击鼠标右键,选择 Options... 选项,如下图:

4.在 Option 窗口中选择 Window ,如下图:

5.找到 UI language 点击下拉框,选中 zh-CN 选项,如下图:

6.点击 Save 按钮保存设置,这样就成功将Git设置成了中文版了:

7.我们回到 Git 终端测试下发现此时菜单已经变成中文了

以上就是W3Cschool编程狮为您演示的“如何将Git的界面语言设置为中文?”,怎么样,你学废了吗?

查看更多关于git教程:

Python–元组&字典

thbcm阅读(180)

元组 tuple

  • tuple 特殊的列表,用 ( ) 标示,一旦建立就不能改变(既不能修改其中的数据项,也不能修改和删除数据项)。
  • 而且只有一个元素时必须在元素后面添加逗号,否则被默认为运算符()

基本说明

tuple(*args, **kwargs)                  将其他元素转换为元组对象

count(value)                            统计值元素个数

index(value, start=None, stop=None)     索引值

内置方法

len(tuple)        计算元组元素个数

max(tuple)      返回元组中元素最大值

min(tuple)       返回元组中元素最小值    

元组方法比较简单,创建后的元组不能改变(这个不是代表变量不能改变,可以指向新的元组值或其他,即不能改变元组内的值。

字典 dict

键值对标示数据,类似 java 的 Map,用 { } 标示。 

这里的键的类型可以是 str(字符串), int(整数), float(浮点数), bool(布尔类型), None(空),使用其他类型无法识别 

例如:a={‘a’: ‘1’, ‘b’: ‘2’, ‘c’: ‘3’}

遍历:

    for key in a:               #   获取键遍历数据

        print(key+’:’+a[key])

    for key in a.keys():        #   获取键后遍历数据

        print(key+’:’+a[key])

    for value in a.values():    #   获取值后遍历数据

        print(value)

    for key,value in a.items(): #   按键和值遍历数据

        print(key+’:’+value)

遍历字典项:

    for kv in a.items():

       print(kv)

基本说明

clear()             清空字典

copy()              复制字典

get(key[,default])  获取键值key对应的值,不存在则返回default,

items()             获取由键和值组成的迭代器

keys()              获取键的迭代器

values()            获取值的迭代器

pop(key)            删除 key:value 成员

update(adict)       从另一个字典更新成员(不存在就创建,存在则覆盖)

update(E=None, **F) 从 dict/iterable E和F更新。

                如果E存在并且具有.keys()方法,则对E中的k执行以下操作:D [k] = E [k]

                如果E存在并且缺少.keys()方法,则执行以下操作:对于k,E中的v:D [k] = v在两种情况下,

                都紧随其后:对于F中的k:D [k] = F [ k]

fromkeys(iter,value)    以列表或元组中的给定的键建立字典,默认值为value

popitem( )          从字典中删除任意一个 key:value 项并返回它

setdefault(*args, **kwargs) 若字典中存在key值为key的,则返回其对应的值;否则在字典中建立一个key:default字典成员

str(dict)           输出字典,以可打印的字符串表示

内置方法

len(dict)           计算字典元素个数,即键的总数

type(variable)      返回输入的变量类型,如果变量是字典就返回字典类型

其他说明

字典比较常用,一般常用的 json 数据转换后的对象基本都是字典类型,使用也比较广泛。但是字典没有切片操作(也不适合切片操作)。

列表推导式是可以使用的,因为实现的方法主要是迭代和性能优化,与 for 循环类型。

推荐好课:Python3 入门Python3 进阶

jQuery基础:处理多个选择结果(each迭代方法)

thbcm阅读(190)

jQuery 提供 .each() 方法来对选中的结果进行循环处理,而且在每次执行函数时,都会给函数传递匹配元素在选中结果里所处位置的数字值作为参数(从零开始的整形变量)。返回 ‘false’ 将停止循环 (就像在普通的循环中使用 ‘break’)。返回 ‘true’ 跳至下一个循环(就像在普通的循环中使用 ‘continue’)。

例子一:

<ul>

<li>第一列</li>

<li>第二列</li>

<li>第三列</li>

</ul>

<button>选中所有列</button>

使用下面的 jQuery 代码,点击按钮后,所有列将被选择,并且在每列后加上 index

$(document).ready(function() {

$(‘button’).click(function(){

$(‘li’).each(function(index){

var str = “<b>”+index+”</b>”;

$(“li:eq(“+index+”)”).append(str);

});

});

});

注意:index 是从零开始的整形变量。

例子二:

<ul>

<li>第一列</li>

<li>第二列</li>

<li class=”mark”>第三列</li>

<li class=”mark”>第四列</li>

</ul>

<button>选择列</button>

使用下面的 jQuery 代码,点击按钮后,class 为“mark”的列将被选择

$(document).ready(function() {

$(‘button’).click(function(){

$(‘li’).each(function(index){

if ($(this).is(“.mark”)){

this.style.color = ‘blue’;

}

});

});

});

如果我们只想选第一个 class 为”mark”的列,可以使用 return false,停止循环

$(document).ready(function() {

$(‘button’).click(function(){

$(‘li’).each(function(index){

if ($(this).is(“.mark”)){

this.style.color = ‘blue’;

return false; //注意这个return

}

});

});

});

注意:在上述例子里,我用到了 $(this) 和 this, 前者是 jQuery 对象,后者是 DOM 对象。jQuery 对象具有is方法,但不具有 style 方法,同理,DOM 对象具有 style 方法,但不具有 is 方法。

Python with和上下文管理工具详解

thbcm阅读(202)

前言

如果你有阅读源码的习惯,可能会看到一些优秀的代码经常出现带有 “with” 关键字的语句,它通常用在什么场景呢?今天就来说说 with 和 上下文管理器。

对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源。

比如 Python 程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件,否则会出现什么情况呢?极端情况下会出现 “Too many open files” 的错误,因为系统允许你打开的最大文件数量是有限的。

同样,对于数据库,如果连接数过多而没有及时关闭的话,就可能会出现 “Can not connect to MySQL server Too many connections”,因为数据库连接是一种非常昂贵的资源,不可能无限制的被创建。

来看看如何正确关闭一个文件。

普通版

def test1():

    f = open(“output.txt”, “w”)

    f.write(“python之禅”)

    f.close()

这样写有一个潜在的问题,如果在调用 write 的过程中,出现了异常进而导致后续代码无法继续执行,close 方法无法被正常调用,因此资源就会一直被该程序占用而无法被释放。那么该如何改进代码呢?

进阶版

def test2():

    f = open(“output.txt”, “w”)

    try:

        f.write(“python之禅”)

    except IOError:

        print(“oops error”)

    finally:

        f.close()

改良版本的程序是对可能发生异常的代码处进行 try 捕获,使用 try/finally 语句,该语句表示如果在 try 代码块中程序出现了异常,后续代码就不再执行,而直接跳转到 except 代码块。而无论如何,finally 块的代码最终都会被执行。因此,只要把 close 放在 finally 代码中,文件就一定会关闭。

高级版

def test3():

    with open(“output.txt”, “w”) as f:

        f.write(“Python之禅”)

一种更加简洁、优雅的方式就是用 with 关键字。open 方法的返回值赋值给变量 f,当离开 with 代码块的时候,系统会自动调用 f.close() 方法, with 的作用和使用 try/finally 语句是一样的。那么它的实现原理是什么?在讲 with 的原理前要涉及到另外一个概念,就是上下文管理器(Context Manager)。

上下文管理器

任何实现了 __enter__() 和 __exit__() 方法的对象都可称之为上下文管理器,上下文管理器对象可以使用 with 关键字。显然,文件(file)对象也实现了上下文管理器。

那么文件对象是如何实现这两个方法的呢?我们可以模拟实现一个自己的文件类,让该类实现 __enter__() 和 __exit__() 方法。

class File():

    def __init__(self, filename, mode):

        self.filename = filename

        self.mode = mode

    def __enter__(self):

        print(“entering”)

        self.f = open(self.filename, self.mode)

        return self.f

    def __exit__(self, *args):

        print(“will exit”)

        self.f.close()

__enter__() 方法返回资源对象,这里就是你将要打开的那个文件对象,__exit__() 方法处理一些清除工作。

因为 File 类实现了上下文管理器,现在就可以使用 with 语句了。

with File(‘out.txt’, ‘w’) as f:

    print(“writing”)

    f.write(‘hello, python’)

这样,你就无需显式地调用 close 方法了,由系统自动去调用,哪怕中间遇到异常,close 方法也会被调用。

contextlib

Python 还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 __enter__ 方法中执行,yield 之后的语句在 __exit__ 方法中执行。紧跟在 yield 后面的值是函数的返回值。 

from contextlib import contextmanager

 

@contextmanager

def my_open(path, mode):

    f = open(path, mode)

    yield f

    f.close()

调用:

with my_open(‘out.txt’, ‘w’) as f:

    f.write(“hello , the simplest context manager”)

总结

Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。

推荐阅读:Python3 入门Python3 进阶

SQL Server AlwaysON从入门到进阶——存储

thbcm阅读(184)

本节讲授关于 SQL Server 存储方面的内容,相对其他小节而言这节比较短。本节会提供一些关于使用群集或非群集系统进程中对存储的利用建议。固然,重点还是集中在对一个标准的 AlwaysOn 可用组配置进程中,对存储的选择和配置上面。

AlwaysOn 的部署首先建立在一个 Windows Server Failover Cluster(WSFC)上。并且每一个服务器通常有一个独立的 SQL Server实例。另外,每一个服务器使用其本地存储来寄存独立的 SQL Server 实例的数据库文件(数据文件、日志文件、备份文件等)。虽然所有火伴节点都属于同一个群集,但是不需要基于硬盘见证或故障转移实例,也没有同享存储的要求。从而避免了 FCI 中的同享存储单点故障风险。但是 AlwaysOn 可用组可使用 FCI 作为可用副本。这个不但又重新引入单点故障的风险,也增加了群集对节点的复杂度。

言归正传,现在来看一下存储系统的核心内容:

本地存储(Localized)

网络存储(Networked)

下面来详细介绍一下:

本地附加存储(Locally Attached Storage):

这类模式下,本地存储是直接连到服务器上,硬盘直接插入硬件背板(backplane),然后连到服务器的主板上。较老的配置可能会包括将通过 68/80 针电缆连接到PCI总线的扩大 RAID 控制器上。

下图是一个典型的本地存储示意图。这是相对来讲路径断和复杂度低的,可提供快速硬盘访问的方式。背板有一个输入输出 BIOS,可以用于控制横跨本地硬盘的 RAID 阵列的硬盘冗余功能,但是由于硬件服务器的限制,通常最大只有 16 个硬盘可用。

这是典型的没有网络存储的节点中的单独存储示意图,在 WSFC 中,没有独立的存储同享给其他节点。这也使得查分节点的物理位置进程中,不需要对存储进行复制。

网络存储(Network Storage):

网络存储可以作为资源提供给多个计算机系统。有一个中央存储库通过下降很狂每一个服务器的多个阵列的接触点从而更加简单地管理这些硬盘。以下图所示,通常系统中有很多服务器通过光纤(Fibre Channel,FC)网络互联,通常也称为“Fabric”。计算机通过一个Host Bus Adapter(HBA卡,主机总线适配器是网络与交换,是能插入计算机或大型主机的板卡),实际上 HBA 卡类似于一个网卡。

各个服务器也能够通过 iSCSI 网络进行互联,这个网络相对较新但带宽受限(1Gbps)。它运行在标准的、隔离的 TCP\IP 网络。服务器通常使用专用网卡,只用于 iSCSI 和 TCP 通讯从而下降负载,意味着iSCSI的流量控制被分摊出来。现代 iSCSI 已可以处理上限为10Gbps 的带宽数据。对 iSCSI 配置的好处之一是它币传统的FC网络更加经济。但是,也不总是这样。

当有很多服务器发送要求给存储进程并从中接收结果时,可以快速发现在 FC 网络中产生了多少流量。正如 TCP\IP 网络那样,你会发现 FC 网络会被堵满。然后存储区域的网络会因此产生性能问题。在复杂的 SAN 配置中,会有多个交换机连接大量的网线和额外电源需求。如上图,可以看到这类情况下数据活动路径和复杂度都明显变大。

在这么长的路径和复杂路由中提出 I/O 要求,会消耗很多事件和其他开消。关于整合的存储,这类系统能提供甚么呢?这类存储可以更容易地调配和交付资源给大范围的数据。然后就像虚拟化,不是每一个实体都可以用。

这类模式的存储也经常使用于 SQL Server 的 FCI 中,LUNs 从磁盘阵列中划出来,而且数量巨大。这里的缺点是阵列可能被以 128KB的块大小格式化。这个大小对 SQL Server 来讲并不是最优化。其优点是,当被公道配置后,存储要求几近可以不到末端阵列。由于要求可以直接产生在高速内存缓冲区,然后缓存的数据在适合的时间点被刷到硬盘从而下降对性能的影响。在停电时,后备电源也会把缓存中的数据刷新到硬盘以避免数据丢失。

另外还有一种网络存储可用于在高可用节点中同享存储又避免多个主机连接的开消。这类存储类型称为 Direct Attached Storage(DAS,直接附加存储),这类系统专门为可使用基于私有光纤连接、基本上可以归到本地化的利用而设计。下面是一个典型的私有高可用存储配置示意图:

这个场景下,如果想创建私有高可用群集,会略微比本地存储更好。一些存储供应商提供通过光纤连接的装备,并可以有最多两个主机连接到高可用方案的多个路径中。多个阵列存储模块可以顺便增加可用存储量。

这类存储也能够用于 SQL Server FCI 中。这类方式合适在特定环境下的小型或简单群集中少数几个节点同享存储之用。你可能已注意到上图中 LUNs 的方式是一个方框,这是由于不是所有的 Windows 系统的逻辑硬盘底层都有独立的物理阵列。上图的情形也是最多见的配置中,磁盘被设置为一个较大的阵列。

想象一个大蛋糕。或在这类情况下,从物理硬盘池中创建的阵列。切下一块蛋糕或从阵列中划出一个 LUN 用于给 Windows 作为逻辑硬盘之用。

Web 中文字体处理总结

thbcm阅读(216)

背景介绍

Web 项目中,使用一个合适的字体能给用户带来良好的体验。但是字体文件太多,如果想要查看字体效果,只能一个个打开,非常影响工作效率。因此,需要实现一个功能,能够根据固定文字以及用户输入预览字体。在实现这一功能的过程中主要解决两个问题:

  • 中文字体体积太大导致加载时间过长
  • 字体加载完成前不展示预览内容

现在将问题的解决以及我的思考总结成文。

使用 web 自定义字体

在聊这两个问题之前,我们先简述怎样使用一个 Web 自定义字体。要想使用一个自定义字体,可以依赖 CSS Fonts Module Level 3 定义的 @font-face 规则。一种基本能够兼容所有浏览器的使用方法如下:

@font-face {
    font-family: "webfontFamily"; /* 名字任意取 */
    src: url('webfont.eot');
         url('web.eot?#iefix') format("embedded-opentype"),
         url("webfont.woff2") format("woff2"),
         url("webfont.woff") format("woff"),
         url("webfont.ttf") format("truetype");
    font-style:normal;
    font-weight:normal;
}
.webfont {
    font-family: webfontFamily;   /* @font-face里定义的名字 */
}

由于 woff2woffttf 格式在大多数浏览器支持已经较好,因此上面的代码也可以写成:

@font-face {
    font-family: "webfontFamily"; /* 名字任意取 */
    src: url("webfont.woff2") format("woff2"),
         url("webfont.woff") format("woff"),
         url("webfont.ttf") format("truetype");
    font-style:normal;
    font-weight:normal;
}

有了@font-face 规则,我们只需要将字体源文件上传至 cdn,让 @font-face 规则的 url 值为该字体的地址,最后将这个规则应用在 Web 文字上,就可以实现字体的预览效果。

但这么做我们可以明显发现一个问题,字体体积太大导致的加载时间过长。我们打开浏览器的 Network 面板查看:

可以看到字体的体积为5.5 MB,加载时间为5.13 s。而夸克平台很多的中文字体大小在20~40 MB 之间,可以预想到加载时间会进一步增长。如果用户还处于弱网环境下,这个等待时间是不能接受的。

一、中文字体体积太大导致加载时间过长

1. 分析原因

那么中文字体相较于英文字体体积为什么这么大,这主要是两个方面的原因:

  1. 中文字体包含的字形数量很多,而英文字体仅包含26个字母以及一些其他符号。
  2. 中文字形的线条远比英文字形的线条复杂,用于控制中文字形线条的位置点比英文字形更多,因此数据量更大。

我们可以借助于 opentype.js,统计一个中文字体和一个英文字体在字形数量以及字形所占字节数的差异:

字体名称 字形数 字形所占字节数
FZQingFSJW_Cu.ttf 8731 4762272
JDZhengHT-Bold.ttf 122 18328

夸克平台字体预览需要满足两种方式,一种是固定字符预览, 另一种是根据用户输入的字符进行预览。但无论哪种预览方式,也仅仅会使用到该字体的少量字符,因此全量加载字体是没有必要的,所以我们需要对字体文件做精简。

2. 如何减小字体文件体积

unicode-range

unicode-range 属性一般配合 @font-face 规则使用,它用于控制特定字符使用特定字体。但是它并不能减小字体文件的大小,感兴趣的读者可以试试。

fontmin

fontmin 是一个纯 JavaScript 实现的字体子集化方案。前文谈到,中文字体体积相较于英文字体更大的原因是其字形数量更多,那么精简一个字体文件的思路就是将无用的字形移除:

// 伪代码
const text = '字体预览'
const unicodes = text.split('').map(str => str.charCodeAt(0))
const font = loadFont(fontPath)
font.glyf = font.glyf.map(g => {
 // 根据unicodes获取对应的字形
})

实际上的精简并没有这么简单,因为一个字体文件由许多表(table)构成,这些表之间是存在关联的,例如 maxp 表记录了字形数量,loca 表中存储了字形位置的偏移量。同时字体文件以 offset table(偏移表) 开头,offset table记录了字体所有表的信息,因此如果我们更改了 glyf 表,就要同时去更新其他表。

在讨论 fontmin 如何进行字体截取之前,我们先来了解一下字体文件的结构:

上面的结构限于字体文件只包含一种字体,且字形轮廓是基于 TrueType 格式(决定 sfntVersion 的取值)的情况,因此偏移表会从字体文件的0字节开始。如果字体文件包含多个字体,则每种字体的偏移表会在 TTCHeader 中指定,这种文件不在文章的讨论范围内。

偏移表(offset table):

Type Name Description
uint32 sfntVersion 0x00010000
uint16 numTables Number of tables
uint16 searchRange (Maximum power of 2 <= numTables) x 16.
uint16 entrySelector Log2(maximum power of 2 <= numTables).
uint16 rangeShift NumTables x 16-searchRange.

表记录(table record):

Type Name Description
uint32 tableTag Table identifier
uint32 checkSum CheckSum for this table
uint32 offset Offset from beginning of TrueType font file
uint32 length Length of this table

对于一个字体文件,无论其字形轮廓是 TrueType 格式还是基于 PostScript 语言的 CFF 格式,其必须包含的表有 cmapheadhheahtmxmaxpnameOS/2post。如果其字形轮廓是 TrueType 格式,还有cvtfpgmglyflocaprepgasp 六张表会被用到。这六张表除了 glyfloca 必选外,其它四个为可选表。

fontmin 截取字形原理

fontmin 内部使用了 fonteditor-core,核心的字体处理交给这个依赖完成,fonteditor-core 的主要流程如下:

1. 初始化 Reader

将字体文件转为 ArrayBuffer 用于后续读取数据。

2. 提取 Table Directory

前文我们说到紧跟在 offset table(偏移表) 之后的结构就是 table record(表记录),而多个 table record 叫做 Table Directoryfonteditor-core 会先读取原字体的 Table Directory,由上文表记录的结构我们知道,每一个 table record 有四个字段,每个字段占4个字节,因此可以很方便的利用 DataView 进行读取,最终得到一个字体文件的所有表信息如下:

3. 读取表数据

在这一步会根据 Table Directory 记录的偏移和长度信息读取表数据。对于精简字体来说,glyf 表的内容是最重要的,但是 glyftable record 仅仅告诉了我们 glyf 表的长度以及 glyf 表相对于整个字体文件的偏移量,那么我们如何得知 glyf 表中字形的数量、位置以及大小信息呢?这需要借助字体中的 maxp 表和 loca(glyphs location) 表,maxp 表的 numGlyphs 字段值指定了字形数量,而 loca 表记录了字体中所有字形相对于 glyf 表的偏移量,它的结构如下:

Glyph Index Offset Glyph Length
0 0 100
1 100 150
2 250 0
n-1 1170 120
extra 1290 0

根据规范,索引0指向缺失字符(missing character),也就是字体中找不到某个字符时出现的字符,这个字符通常用空白框或者空格表示,当这个缺失字符不存在轮廓时,根据 loca 表的定义可以得到 loca[n] = loca[n+1]。我们可以发现上文表格中多出了 extra 一项,这是为了计算最后一个字形 loca[n-1] 的长度。

上述表格中 Offset 字段值的单位是字节,但是具体的字节数取决于字体 head 表的 indexToLocFormat 字段取值,当此值为0时,Offset 100 等于 200 个字节,当此值为1时,Offset 100 等于 100 个字节,这两种不同的情况对应于字体中的 Short versionLong version

但是仅仅知道所有字形的偏移量还不够,我们没办法认出哪个字形才是我们需要的。假设我需要字体预览这四个字形,而字体文件有一万个字形,同时我们通过 loca 表得知了所有字形的偏移量,但这一万里面哪四个数据块代表了字体预览四个字符呢?因此我们还需要借助 cmap 表来确定具体的字形位置,cmap 表里记录了字符代码(unicode)到字形索引的映射,我们拿到对应的字形索引后,就可以根据索引获得该字形在 glyf 表中的偏移量。

而一个字形的数据结构以 Glyph Headers 开头:

Type Name Description
int16 numberOfContours the number of contours
int16 xMin Minimum x for coordinate data
int16 yMin Maximum y for coordinate data
int16 xMax Minimum x for coordinate data
int16 yMax Maximum x for coordinate data

numberOfContours 字段指定了这个字形的轮廓数量,紧跟在 Glyph Headers 后面的数据结构为 Glyph Table

在字体的定义中,轮廓是由一个个位置点构成的,并且每个位置点具有编号,这些编号从0开始按升序排列。因此我们读取指定的字形就是读取 Glyph Headers 中的各项值以及轮廓的位置点坐标。

Glyph Table 中,存放了每个轮廓的最后一个位置点编号构成的数组,从这个数组中就可以求得这个字形一共存在几个位置点。例如这个数组的值为[3, 6, 9, 15],可以得知第四个轮廓上最后一个位置点的编号是15,那么这个字形一共有16个位置点,所以我们只需要以16为循环次数进行遍历访问 ArrayBuffer 就可以得到每个位置点的坐标信息,从而提取出了我们想要的字形,这也就是 fontmin 在截取字形时的原理。

另外,在提取坐标信息时,除了第一个位置点,其他位置点的坐标值并不是绝对值,例如第一个点的坐标为[100, 100],第二个读取到的值为[200, 200],那么该点位置坐标并不是[200, 200],而是基于第一个点的坐标进行增量,因此第二点的实际坐标为[300, 300]

因为一个字体涉及的表实在太多,并且每个表的数据结构也不一样。这里无法一一列举 fonteditor-core 是如何处理每个表的。

4. 关联glyf信息

在使用了 TrueType 轮廓的字体中,每个字形都提供了 xMinxMaxyMinyMax 的值,这四个值也就是下图的Bounding Box。除了这四个值,还需要 advanceWidthleftSideBearing 两个字段,这两个字段并不在 glyf 表中,因此在截取字形信息的时候无法获取。在这个步骤,fonteditor-core 会读取字体的 hmtx 表获取这两个字段。

5. 写入字体

在这一步会重新计算字体文件的大小,并且更新偏移表(Offset table)表记录(Table record)有关的值, 然后依次将偏移表表记录表数据写入文件中。有一点需要注意的是,在写入表记录时,必须按照表名排序进行写入。例如有四张表分别是 prephmtxglyfhead、则写入的顺序应为 glyf -> head -> hmtx -> prep,而表数据没有这个要求。

fontmin 不足之处

fonteditor-core 在截取字体的过程中只会对前文提到的十四张表进行处理,其余表丢弃。每个字体通常还会包含 vheavmtx 两张表,它们用于控制字体在垂直布局时的间距等信息,如果用 fontmin 进行字体截取后,会丢失这部分信息,可以在文本垂直显示时看出差异(右边为截取后):

fontmin 使用方法

在了解了 fontmin 的原理后,我们就可以愉快的使用它啦。服务器接受到客户端发来的请求后,通过 fontmin 截取字体,fontmin 会返回截取后的字体文件对应的 Buffer,别忘了 @font-face 规则中字体路径是支持 base64 格式的,因此我们只需要将 Buffer 转为 base64 格式嵌入在 @font-face 中返回给客户端,然后客户端将该 @font-face 以 CSS 形式插入 <head></head> 标签中即可。

对于固定的预览内容,我们也可以先生成字体文件保存在 CDN 上,但是这个方式的缺点在于如果 CDN 不稳定就会造成字体加载失败。如果用上面的方法,每一个截取后的字体以 base64 字符串形式存在,则可以在服务端做一个缓存,就没有这个问题。利用 fontmin 生成字体子集代码如下:

const Fontmin = require('fontmin')
const Promise = require('bluebird')


async function extractFontData (fontPath) {
  const fontmin = new Fontmin()
    .src('./font/senty.ttf')
    .use(Fontmin.glyph({
      text: '字体预览'
    }))
    .use(Fontmin.ttf2woff2())
    .dest('./dist')


  await Promise.promisify(fontmin.run, { context: fontmin })()
}
extractFontData()

对于固定预览内容我们可以预先生成好分割后的字体,对于用户输入的动态预览内容,我们当然也可以按照这个流程:

获取输入 -> 截取字形 -> 上传 CDN -> 生成 @font-face -> 插入页面

按照这个流程来客户端需要请求两次才能获取字体资源(别忘了在 @font-face 插入页面后才会去真正请求字体),并且截取字形上传 CDN 这两步时间消耗也比较长,有没有更好的办法呢?我们知道字形的轮廓是由一系列位置点确定的,因此我们可以获取 glyf 表中的位置点坐标,通过 SVG 图像将特定字形直接绘制出来。

SVG 是一种强大的图像格式,可以使用 CSSJavaScript 与它们进行交互,在这里主要应用了 path 元素

获取位置信息以及生成 path 标签我们可以借助 opentype.js 完成,客户端得到输入字形的 path 元素后,只需要遍历生成 SVG 标签即可。

3. 减小字体文件体积的优势

下面附上字体截取后文件大小和加载速度对比表格。可以看出,相较于全量加载,对字体进行截取后加载速度快了145 倍。

fontmin 是支持生成 woff2 文件的,但是官方文档并没有更新,最开始我使用的 woff 文件,但是 woff2 格式文件体积更小并且浏览器支持不错

字体名称 大小 时间
HanyiSentyWoodcut.ttf 48.2MB 17.41s
HanyiSentyWoodcut.woff 21.7KB 0.19s
HanyiSentyWoodcut.woff2 12.2KB 0.12s

二、字体加载完成前不展示预览内容

这是在实现预览功能过程中的第二个问题。

在浏览器的字体显示行为中存在阻塞期交换期两个概念,以 Chrome 为例,在字体加载完成前,会有一段时间显示空白,这段时间被称为阻塞期。如果在阻塞期内仍然没有加载完成,就会先显示后备字体,进入交换期,等待字体加载完成后替换。这就会导致页面字体出现闪烁,与我想要的效果不符。而 font-display 属性控制浏览器的这个行为,是否可以更换 font-display 属性的取值来达到我们的目的呢?

font-display

Block Period Swap Period
block Short Infinite
swap None Infinite
fallback Extremely Short Short
optional Extremely Short None

字体的显示策略和 font-display 的取值有关,浏览器默认的 font-display 值为 auto,它的行为和取值 block 较为接近。

第一种策略是 FOIT(Flash of Invisible Text)FOIT 是浏览器在加载字体的时候的默认表现形式,其规则如前文所说。

第二种策略是 FOUT(Flash of Unstyled Text)FOUT 会指示浏览器使用后备字体直至自定义字体加载完成,对应的取值为 swap

两种不同策略的应用:Google Fonts FOIT 汉仪字库 FOUT

在夸克项目中,我希望的效果是字体加载完成前不展示预览内容,FOIT 策略最为接近。但是 FOIT 文本内容不可见的最长时间大约是3s, 如果用户网络状况不太好,那么3s过后还是会先显示后备字体,导致页面字体闪烁,因此 font-display 属性不满足要求。

查阅资料得知,CSS Font Loading API JavaScript 层面上也提供了解决方案:

FontFace、FontFaceSet

先看看它们的兼容性:

又是 IE,IE 没有用户不用管

我们可以通过 FontFace 构造函数构造出一个 FontFace 对象:

const fontFace = new FontFace(family, source, descriptors)

  • family
    • 字体名称,指定一个名称作为 CSS 属性 font-family 的值,
  • source
    • 字体来源,可以是一个 url 或者 ArrayBuffer
  • descriptors optional
    • style:font-style
    • weight:font-weight
    • stretch:font-stretch
    • display: font-display (这个值可以设置,但不会生效)
    • unicodeRange:@font-face 规则的 unicode-ranges
    • variant:font-variant
    • featureSettings:font-feature-settings

构造出一个 fontFace 后并不会加载字体,必须执行 fontFaceload 方法。load 方法返回一个 promisepromiseresolve 值就是加载成功后的字体。但是仅仅加载成功还不会使这个字体生效,还需要将返回的 fontFace 添加到 fontFaceSet

使用方法如下:

/**
  * @param {string} path 字体文件路径
  */
async function loadFont(path) {
  const fontFaceSet = document.fonts
  const fontFace = await new FontFace('fontFamily', `url('${path}') format('woff2')`).load()
  fontFaceSet.add(fontFace)
}

因此,在客户端我们可以先设置文字内容的 CSS 为 opacity: 0, 等待 await loadFont(path) 执行完毕后,再将 CSS 设置为 opacity: 1, 这样就可以控制在自定义字体加载未完成前不显示内容。

最后总结

本文介绍了在开发字体预览功能时遇到的问题和解决方案,限于 OpenType 规范条目很多,在介绍 fontmin 原理部分,仅描述了对 glyf 表的处理,对此感兴趣的读者可进一步学习。

本次工作的回顾和总结过程中,也在思考更好的实现,如果你有建议欢迎和我交流。同时文章的内容是我个人的理解,存在错误难以避免,如果发现错误欢迎指正。

感谢阅读!

参考

作者:林林

来源: 凹凸实验室

程序员如何优雅的挣零花钱?

thbcm阅读(253)

注:本文转载自Github,原文地址为https://github.com/easychen/howto-make-more-money

虽然程序员有女朋友的不多(误),但是开销往往都不小。 VPS、域名、Mac上那一堆的收费软件、还有Apple每年更新的那些设备,经常都是肾不够用的节奏。

幸好作为程序员,我们有更多挣钱的姿势。

有同学该嚷了:不就是做私单嘛。 对,但是也不太对。做私单的确是一个简单直接方式,但在我看来,私单的投入产出比很差,并不是最优的。 但既然提到了,就先说说吧。

私单

最理想的单子还是直接接海外的项目,比如http://freelance.com 等网站。一方面是因为挣的是美刀比较划算,之前看到像给WordPress写支付+发送注册码这种大家一个周末就能做完的项目,也可以到200~300美刀;另一方面是在国外接单子比较隐蔽。

常用国外网站:

(由ahui132同学补充)

本段由tvvocold同学贡献。 国内也有一个软件众包平台 CODING 码市 。 码市基于云计算技术搭建的云端软件开发平台 http://Coding.net 作为沟通和监管工具,快速连接开发者与需求方,旨在通过云端众包的方式提高软件交付的效率。码市作为第三方监管平台,会对所有项目进行审核以保证项目需求的明确性,并提供付款担保,让开发者只要按时完成项目开发即可获取酬劳。你可以 在这里 看到开发者对码市的评价。

当然,猪八戒这种站我就不多说了,不太适合专业程序员去自贬身价。

按需雇用

按需雇用是近几年新兴的私单方式,开发者在业余时间直接到雇主公司驻场办公,按时薪领取报酬。这种方式省去了网络沟通的低效率,也避免了和雇主的讨价还价,适合怕麻烦的程序员。

1. 拉勾大鲲

大鲲 由拉勾网推出,考虑到拉勾上三十多万的招聘方,大鲲不缺雇主,这是其他独立平台相对弱势的地方。

2. 实现网

的价格也很不错,但是我强烈建议大家不要在介绍中透漏实名和真实的公司部门信息,因为这实在太高调了。有同学说,这是我的周末时间啊,我爱怎么用就怎么用,公司还能告我怎么的? 虽然很多公司的劳动合同里边并不禁止做兼职,但在网上如此高调的干私活,简直就是在挑衅HR:「我工作不饱和」、「公司加班不够多」… 再想象下你一边和产品经理说这个需求做不完,一边自己却有时间做私单的样子。你自己要是老板也不愿提拔这样的人吧。

(我这几天重新去看了下,人才页面已经不再显示姓名了,只用使用头像。这样只要在工作经历介绍里边注意一点,就可以避免上述问题了。)

3. 程序员客栈

不太熟悉,但国内按需雇用的网站不多,写出来供大家参考。

Side Project

比起做私单,做一个Side Project会更划算。

Side Project的好处是你只需要对特定领域进行持续投入,就可以在很长时间获得收入。这可以让你的知识都在一棵树上分支生长,从而形成良好的知识结构,而不是变成一瓶外包万金油。

思路有两种:

一种是做小而美的,针对一个细分领域开发一个功能型应用,然后放到市场上去卖;

另一种是做大而全的基础应用(想想WordPress),方便别人在上边直接添加代码,定制成自己想要的应用。

前一种做起来比较快,但需要自己去做一些销售工作;后一种通常是开源/免费+收费模式,推广起来更简单。

有同学会说,我写的 Side Project 就是卖不掉啊。项目方向的选取的确是比较有技巧的,但简单粗暴的解决方案就是找一个现在卖得非常好、但是产品和技术却不怎样的项目,做一个只要一半价格的竞品。

比如 Mac 下有一个非常有名的写作软件,叫 Ulysses 。我试用了一下非常不错,但就是贵,283 RMB。后来看到了 Mweb ,光是免费的 Lite 版覆盖了 Ulysses 的主功能,完整版也才98RMB,几乎没有思考就买下来了。

做咨询

1. 专家平台

如果你在技术圈子里边小有名气,或者在某一个业务上特别精通,那么通过做咨询来挣钱是一种更轻松的方式。和人在咖啡厅聊几个小时,几百上千块钱就到手了。

国内这方面的产品,我知道的有下边几个:

  • 在行: 这个是果壳旗下的,做得比较早,内容是全行业的,所以上边技术向的反而不多。
  • 缘创派: 缘创派的轻合伙栏目,主要面向创业者,适合喜欢感受创业氛围的技术专家们。
  • 极牛: 你可以认为这是一个程序员版本的「在行」,我浏览了下,虽然被约次数比在行要低不少,但专业性比较强,期望他们能尽快的推广开来吧。
  • 知加:这个项目是我参与的,面向程序员,类似「分答」的付费语音问答,刚开始内测,上边有一些硅谷科技公司的同学。感兴趣的可以看看。

做咨询虽然也是实名的,但和私活是完全不同的。咨询的时间短,不会影响到正常的休息,更不会影响上班;而且大部分公司是鼓励技术交流的,所以大家的接受度都很高。

2. 付费社群

除了APP外,我觉得收费群也是可以做的。比如可以搞一个技术创业群,找一些创业成功的同学、做投资的同学、做法务的同学,面向想创业的同学开放,每人收个几百块的年费。然后你在创业过程中遇到的问题,都可以有人解答,不会觉得是孤零零的一个人。如果遇到了问题,群里的人可以解答;如果没遇到问题,那不是更好么。有种卖保险的感觉,哈哈哈。

比较好用的工具是 知识星球 也就是之前的小密圈。这个工具比较适合交流和讨论,长文比较痛苦。可以发布到其他地方,然后粘贴回来。

另一个靠谱的工具大概是微博的 V+ 会员。说它靠谱主要是它在微博上,所以等于整合了 「内容分发」→ 「新粉丝获取」 → 「付费用户转化」 的整个流程。

PS:交流型付费社群的一个比较难处理的事情是,很难平衡免费的粉丝和付费的社群之间的关系,所以我最后的选择是付费类的提供整块的内容,比如整理成册的小书、录制的实战视频等;而日常零碎的资料分享还是放到微博这种公开免费的平台。

写文章

1. 投稿

很多同学喜欢写技术博客,其实把文章投给一些网站是有稿费的。 比如InfoQ,他们家喜欢收3000~4000字的深度技术文章;稿费是千字150。虽然不算太多,但一篇长文的稿费也够买个入门级的Cherry键盘了。我喜欢InfoQ的地方是他们的版权要求上比较宽松。文章在他们网站发布后,你可以再发布到自己博客的;而且文章可以用于出书,只要标明原发于InfoQ即可。

更详细的说明见这里:http://www.infoq.com/cn/article-guidelines

微博的@rambone同学补充到,文章还可以发到微博、微信、简书等支持打赏的平台。考虑到简书CEO及其官博对程序员的奇葩态度,个人建议是换个咱程序员自己的平台写文章。

2. 出版

顺便说一句,比起写文章,其实通过传统发行渠道出书并不怎么挣钱,我之前到手的版税是8%,如果通过网络等渠道销售,数字会更低。出电子书收益会好一些,我之前写过一篇文章专门介绍:《如何通过互联网出版一本小书》

以前一直写图文为主的书,用Markdown非常不错;但最近开始写技术教程类的书,发现Markdown不太够用了,最主要的问题有 ① 不支持视频标签,尤其是本地视频方案 ② 代码高亮什么的很麻烦 ③ 也没有footer note、文内说明区域的预置。

这里要向大家严重推荐Asciidoc,你可以把它看成一个增强版的Markdown,预置了非常多的常用格式,而且GitBook直接就支持这个格式(只要把.md 搞成 .adoc 就好),Atom也有实时预览插件。用了一段时间,非常喜欢。

付费文集

最近一年有不少的付费文集产品出现,可以把它看成传统出版的一个网络版。一般是写作十篇以内的系列文章,定价为传统计算机书的一半到三分之一。付费文集产品通常是独家授权,所以在选择平台方面一定要慎重,不然一个好作品可能就坑掉了。

1. 掘金小册

小册是由掘金推出的付费文集产品。我是小册的第一批作者,一路用下来还是很不错的。文章格式直接采用 Markdown , 发布以后可以实时更新,保证内容的新鲜度,非常的方便。小册的一般定价在19~29,通用内容销量好的能过千,细分内容基本也能过百。挣零花钱的话,是个非常不错的选择。

2. 达人课

是 GitChat 旗下的付费文集产品,现在应该已经合并到 CSDN 了。GitChat 的用户群不算大,但付费意愿还可以,大概因为内容就没有免费的 。之前我上课的时候是提交完成以后的文档给编辑,由编辑同学手动上架。感觉比较麻烦,尤其是修改错别字什么的。

3. 小专栏

这个平台不熟……写到这里仅供参考。

教学视频

微博的@瓜瓜射门啦同学给了自己应聘程序教学网站讲师的经验:应聘程序教学网站讲师,出视频+作业教程,平台按小时支付,这个不知道算不算挣零花钱,我算了一下去年,一年大概出 20 个小时视频,拿到手是不到 6 万的样子,平时就是周末花时间弄下。

在线教育起来以后,录制教学视频也可以赚钱了。关于录制在线课程的收益,一直不为广大程序员所知。但最近和51CTO学院和 网易云课堂 的同学聊天,才发现一个优秀的40~60节的微专业课程,一年的收益比得上一线城市高级总监的收入。难怪最近做培训的人这么多

渠道和分成

大部分的平台合同有保密协议,所以不能对外讲。但网易云课堂和Udemy在公开的讲师注册协议中写明了分成,所以这里说一下。

网易云课堂

网易的课分三类:

  • 独立上架:等于网易提供平台(视频上传管理、用户管理、支付系统等),由你自己来负责营销。这个分成比例在 9:1 ,平台收取 10% 的技术服务费。我觉得非常划算。
  • 精品课:由网易帮你推广,但需要和他们签订独立的合同,会收取更多的分成。最麻烦的是,通常是独家授权。一旦签署了,就不能在其他平台上架课程了。
  • 微专业:这个是网易自己规划的课程体系,从课程的策划阶段就需要和他们深度沟通。也是网易推广力度最大、收益最大的一类课程。

方糖全栈课就放在网易平台上,觉得好的地方如下:

  • 支付渠道相对全,还支持花呗,这样对于我这种高价课就非常重要。苹果应用内购买课程会渠道费用会被苹果扣掉30%,好想关掉
  • 自带推广系统,愿意的话可以用来做课程代理系统。

Udemy

相比之下 Udemy 就很贵了,分成是 5:5 ;支付上国内用户只能通过信用卡或者银行卡绑 paypal 支付。但可以把课程推向全球。(但我英文还不能讲课 )

腾讯课堂没用过,欢迎熟悉的同学 PR 。

小课和大课

我个人喜欢把视频分成小课和大课两种。因为视频虽然看起来时间短,但实际上要做好的话,背后要消耗的时间、要投入精力还是非常多的。大课动不动就是几十上百个课时,绝大部分上班的程序员都没有时间来录制。所以挣零花钱更适合做小课,这种课一般就几个小时,剪辑成 10 个左右的小课时,价格在几十百来块。如果是自己专业纯熟的领域,一个长假就可以搞定。

表现形式

在课程的表现形式上,我个人更喜欢http://designcode.io 这种图文+视频的模式,一方面是学习者可以快速的翻阅迅速跳过自己已经学会的知识;另一方面,会多出来 微博头条文章、微信公众号、知乎和简书专栏这些长文推广渠道。

当然,内容本身才是最核心的。现在那么多的免费视频,为什么要来买你的收费版?

其实现在绝大部分教学视频,往往都真的只是教学,和现实世界我们遇到的问题截然不同。里边都是一堆简化后的假项目,为了教学而刻意设计的。

这里和大家分享一个我之前想操作的想法。

就是在自己决定开始做一个开源项目后,用录屏软件把整个过程完完整整的录下来。开源的屏幕录制工具OBS,1920*1080的屏幕录成FLV格式,一个小时只需要1G,一个T的移动硬盘可以录制上千小时,对一个中型项目来说也足够了。

等项目做完,就开源放到GitHub,让大家先用起来。等迭代稳定后,再从录制的全量视频中剪辑出一系列的教程,整理出一系列的文章,放到网站上做收费课程。

这样做有几个好处:

  • 保证所有遇到的问题都是真实的,不是想象出来的,学习过这个课程的人,可以独立的将整个项目完整的实现。
  • 没有特意的录制过程,所以教程其实是软件开发的副产品,投入产出比更高。
  • 如果你的软件的确写得好,那么用过你软件的人可以成为你的客户或者推荐员。

后续

今年我录制方糖全栈课的时候就采用了上边这个思路,效果还不错,不过有几个小问题:

  • 连续性。录着视频写代码总会有一种潜在焦虑,平时经常写一会儿休息一会儿,录像时就会留下大段的空白,有点浪费空间。当然这个主要是心理上的。
  • 录音。录音的问题更大一些。因为一个长期项目很难一直处于一个安静的环境,另外基础课录制可能需要大量的讲解,几个小时写下来嗓子哑了。 最后的解决方式是剪辑的时候重新配音,不过需要注意音画同步问题。

软件

如果是没有太多界面切换的课程,那可以使用keynote自带的录音。在其他环境里边的演示的视频可以直接粘贴到keynote里面来播放。

但是当你有很多的外部界面的时候,就需要录屏了。mac上可以直接用quicktime来录制。文件,新建里边选 record screen就好。

我录全栈课的时候,因为会在三个操作系统上录一些界面,所以我选择了obs。虽然这个工具主打的是直播,但实际上它的录制功能也还是挺不错的。

剪辑的话,用mac的imovie基本就够了,主要用到的功能就是分割片段,然后把不要的删掉。音频去下底噪。部分等待时间过长的片段加速下。当然adobe家的也行,就是贵。

硬件

硬件上边,最好买一个用来支撑话筒的支架。不要用手直接握着话筒来录,这样就不会有电流声(或者很小)。外接声卡我用的是 XOX , 在 Mac 下边效果挺好,但不知道为啥在 Windows 上回声比较大(当然也可能是系统设置的原因)。

内部推荐和猎头推荐

如果你在BAT等一流互联网公司工作,如果你有一帮志同道合的程序员朋友,那么基本上每隔几个月你应该就会遇到有想换工作的同事和朋友,所以千万别错过你挣推荐费的大好时机。

一般来讲,公司内部推荐的钱会少一些,我见过的3000~6000的居多。但因为是自己公司,会比较靠谱,所以风险小一些。经常给公司推荐人才,还会提升老大多你的好感度,能优先就优先吧。

比起内部推荐,猎头推荐的推荐费则会多很多。一个30万年薪的程序员,成功入职后差不多可以拿到1万RMB的推荐费。但猎头渠道的问题在于对简历质量要求非常高,有知名公司背景的才容易成单;回款周期又特别长,一般要入职过了试用期以后才能拿到全部推荐费,得小半年。

小结

学会挣钱是一件非常重要的事情,它会让你了解商业是如何运作的,帮助你理解公司的产品逻辑、以及为你可能的技术创业打下坚实的基础。

所以我鼓励大家多去挣零花钱,最好各种姿势都都试试,会有意想不到的乐趣。 如果你有更好的挣零花钱技能,欢迎发PR过来,我会挑不错的合并进去 :)

程序员如何赚外快?

thbcm阅读(189)

最近这个话题由一篇《程序员如何优雅的挣零花钱 》而起,想一想360行,行行都能写出个所以然来,然而依旧没有多少人能够挣到零花钱。借助本文,详细说说程序员赚钱的渠道有哪些?

1.自己做网站

通过广告或者拉赞助来赚钱,这一途径还是比较容易的,就是来钱慢,而且还要花费的时间较多。

2.接单子

有能力,赚钱不是问题,君不见现在越来越多人选择自由职业,接外包项目做,一个项目可能做下来有几万块都有,根据自身的特长和优势在网上接单。可以在专门的自由职业的网站上查阅接单。

3.写文章

很多网站也有付费文章,付费译文,通过将自己的技术分享给别人的同时还能收到稿费;还有比如现在很火的自媒体,写出文章,有一个打赏的功能。

4.制作APP

一个好的应用,可以选择付费,免费的应用如果用的人多还可以加进推广,从而获得报酬。

5.制作主题

比如付费WordPress主题,在国外,WordPress付费主题都很受欢迎,很多开发者都自己单干,做好一个主题能够持续更新能一直赚钱。这也不失为一个好的方法,当你做的主题越多,来的钱也会越多。

6.录制视频

在线教育的兴起,越多的人加入编程这个行业,有经验的人可以通过录制视频,可以选择付费,也可以投放在IT技术平台赚钱。

7.做咨询

如果你是专家可以通过给某些公司做服务咨询。

8.收学徒

君不见,现在收徒一个3w的都有,也许收的徒弟多了,还可以实现上市之梦。。。

9.做微商

随着微服务崛起,微商也在行业中逐渐兴起,微商赚的是熟人生意,有人际关系,当然就能赚钱。

10.猎头或内推

随着互联网行业抢人才的兴起,猎头也成为一个很火的职业,推荐一个人成功入职就能拿到几千佣金不等。如果公司内部有内推职位,你还可以通过这个途径来引荐赚推荐费。 总结: 以上是比较常见的,有能力当然自己做老板创业,还可以到BAT打工,前提是你内功厚。

来源:http://caibaojian.com/programer-earn-money.html

Python之os模块功能全解

thbcm阅读(176)

前言

os模块:是对操作系统的调用,模拟对操作系统的指令 

os 模块提供了一种使用操作系统相关功能的便携方式。如果您只想读取或写入文件,使用 open() 方法;如果要操作路径,使用 os.path 模块;如果要读取命令行中所有文件中的所有行 ,使用 fileinput 模块;有关创建临时文件和目录的信息,使用 tempfile 模块;有关高级文件和目录的处理,使用 shutil 模块。

下面对经常使用的功能进行介绍;

全解

1. 获取当前路径及路径下的文件

os.getcwd():查看当前所在路径。
os.listdir(path): 列举目录下的所有文件。返回的是列表类型。

import os

cp = os.getcwd()  # 获取当前路径, 返回绝对路径

print(cp)  # C:\Users\Desktop\w3cschool\os

fileList = os.listdir(cp)  # 获取路径下的文件,以及文件夹

print(fileList)  # [‘os_.py’, ‘sub’, ‘test.txt’]

2. 获取路径的绝对路径

os.path.abspath(path): 返回path的绝对路径

abspath1 = os.path.abspath(“.”)  # 返回当前路径的绝对路径

print(abspath1)  # C:\Users\Desktop\w3cschool\os

abspath2 = os.path.abspath(“../”)  # 返回上一层路径的绝对路径

print(abspath2)  # C:\Users\Desktop\w3cschool

3. 查看路径的文件夹部分和文件名部分

os.path.split(path): 将路径分解为(文件夹,文件名),返回的是元组类型。可以看出,若路径字符串最后一个字符是,则只有文件夹部分有值;若路径字符串中均无,则只有文件名部分有值。若路径字符串有\,且不在最后,则文件夹和文件名均有值。且返回的文件夹的结果不包含.

os.path.join(path1,path2,…): 将 path 进行组合,若其中有绝对路径,则之前的 path 将被删除。

catalogue_and_file1 = os.path.split(‘D:\pythontest\ostest\Hello.py’)

print(catalogue_and_file1)  # (‘D:\\pythontest\\ostest’, ‘Hello.py’)

catalogue_and_file2 = os.path.split(‘.’)

print(catalogue_and_file2)  # (”, ‘.’)

catalogue_and_file3 = os.path.split(‘D:\pythontest\ostest\\’)

print(catalogue_and_file3)  # (‘D:\\pythontest\\ostest’, ”)

path_join1 = os.path.join(‘D:\pythontest’, ‘ostest’)

print(path_join1)  # D:\pythontest\ostest

path_join2 = os.path.join(‘D:\pythontest\ostest’, ‘hello.py’)

print(path_join2)  # D:\pythontest\ostest\hello.py

path_join3 = os.path.join(‘D:\pythontest\\b’, ‘D:\pythontest\\a’)

print(path_join3)  # D:\pythontest\a

os.path.dirname(path):返回path中的文件夹部分,结果不包含’’;
os.path.basename(path):返回path中的文件名。

4.os.path模块详解

#获取

os.path.abspath()#获取某一个文件的绝对路径

os.path.getatime()#返回path所指的文件或目录的最后的存取时间,时间戳

os.path.getmtime()#返回path所指的文件或目录的最后的修改时间,时间戳

#以下三个可以不考虑路径是否存在

os.path.split(‘C:\a\tes.txt’)#返回一个元组,两部分,一个目录,一个文件名

os.path.dirname(r’C:\a\tex.txt’)#获取文件的绝对路径

os.path.basename(r’C:\a\tes.txt’)#获取最后的值

#判断是否存在

os.path.exists(r’C:’)#判断路径是否存在

os.path.isabs(r’C:\a’)#判断是否一个绝对路径

os.path.isfile(r’C:\a\tes.txt’)#判断是否一个文件

os.path.isdir()#是否是文件夹

#将多个部分组合成一个路径

os.path.join(r’C:’,r‘\b’,r’\a.txt’)#将多个路径组合返回

推荐好课:Python3 入门Python3 进阶

联系我们