程序员,你还能干几年?

技术的浪潮奔腾不息,浪潮退去之后,留给我们自己的是方法论?还是过时的技术?

----

我们程序员,总喜欢谈论所谓“35岁魔咒”,好像“35岁魔咒”成了程序员的专利。然而事实上,35岁,是各行各业的职场人都会面临的问题。

究其根本,35岁是人到中年的转折点,如果我们在工作中的价值产出,更多依靠体力,那么就势必面临职业发展开始走下坡路的困境。

编程,原本是一种极富创造力,重视抽象思维与逻辑推演的工作,但我们相当一部分人,把编程做的像事务性工作,平淡如水,如果这样,“35岁魔咒”就会困扰我们。

在一个社会群体中,不同的人有不同的选择,我无意也无法改变大的环境,但希望帮助有意愿与“35岁魔咒”斗争的程序员,找到一点方向,多干几年!

从一道面试题说起

杨辉三角是我在面试中经常会问的一个问题,”请编程,按照指定格式输出前N行的杨辉三角"。

 

大部分候选人,拿到问题之后,就开始编码,首先写一个两层的循环,然后开始思考循环的终止条件,经过一番思考,给出一个自己都不相信的答案。我印象比较深刻的一次,候选人来自微信支付,也仅给出了获取前N行数据的方法,仍然未能找到按照指定格式打印的方法。

很多人会说,离开学校好几年了,算法早都忘记了,这种问题应该去问校招的同学。我不以为然,我在意的不是一个具体的算法,而是分析和解决问题的思路。我们可以忘记一些具体的算法,但忘记的同时,应该把算法中蕴含的解决问题的思路内化成自己的武器。

接下来,我和大家介绍下我如何看待这个问题。

图灵奖得主N.Wirth提出"程序 = 算法 + 数据结构",我更习惯说“程序 = 数据 + 过程”。

数据视角

首先,我们站在数据的视角,来审视这道面试题,可以发现:

1、可以使用二维数组(以php语言为例)描述杨辉三角的前N行数据

[
  [1],
  [1, 1],
  [1, 2, 1],
  [1, 3, 3, 1],
]

2、行的数据之间,存在如下规则:

1)每行两边数字恒为1

2)每行中间的数字,是上一行交叉位置两个数字的和

因此,可以用一个伪代码描述这种关系:

F(1) = [1]
F(N) = [
    1,
    F(N-1)[0] + F(N-1)[1],
    F(N-1)[1] + F(N-1)[2],
    ...,
    F(N-1)[N-2] + F(N-1)[N - 1],
    1
]

过程视角

接下来,我们站在过程视角,审视这道面试题。首先,它的过程可以分为两个步骤:1)获取前N行杨辉三角的数据;2)按照图示对齐的方式进行打印。

第一步,基于我们前面对数据的分析,可以轻易的得出对应的方法设计:

function yanghui_datas($n); // 获取前N行杨辉三角数据
function yanghui_line_datas($prev_line_data); // 依据前一行数据获取下一行数据

第二步,依据图示对齐方式进行打印。单纯的打印是很简单的,所以,关键在于“对齐”。

解决对齐的方法,无非就是给相应的位置留白。但我们发现,该怎么留白是不清晰的。那就进一步思考,导致留白逻辑不清晰的难点问题是什么?

通过分析,可以看到,如果只需要输出前5行,那么每个数字都是个位数,如果要输出第6行,那么就开始有十位数。因此,是数字宽度的不确定性,产生了这里的困难。

逆向思维,如果数字的宽度确定,那就没有这个困难。所以:

 

我们把每个数字放到一个等宽的格子中。这样,整个问题就清晰了:

格子宽度 = 最大数字宽度 + 1
行前留白 = (LINE_COUNT - LINE_NO) * 格子宽度 / 2

格子内部: (数字居中)
左留白 = (格子宽度 - 数字宽度)  / 2
右留白 = 格子宽度 - 左留白 - 数字宽度

编码

经过上面的分析,代码几乎就跃然纸上了。

// 获取前N行杨辉三角数据
function yanghui_datas($n_line) {
    $datas = []; 
    $prev_line_datas = []; 
    while ($n_line -- > 0) {
        $line_datas = yanghui_line_datas($prev_line_datas);
        array_push($datas, $line_datas);
        $prev_line_datas = $line_datas;
    }   
    return $datas;
}

// 通过前一行数据获取下一行数据
function yanghui_line_datas($prev_line_datas) {
    if (empty($prev_line_datas)) {
        return [1];
    }   

    $line_datas = []; 

    array_push($line_datas, 1); // 行首
    for ($i = 1; $i < count($prev_line_datas); $i ++) {
        array_push($line_datas, $prev_line_datas[$i - 1] + $prev_line_datas[$i]);
    }   

    array_push($line_datas, 1); // 行尾

    return $line_datas;
}

// 打印指定的杨辉三角数据
function yanghui_print($datas) {
    $space         = '&nbsp;'; // 留白字符
    $newline       = '<br />'; // 换行符

    $max_num       = yanghui_max_num($datas); // 最大数字
    $max_num_width = yanghui_num_width($max_num); // 最大宽度
    $unit_width    = $max_num_width + 1; // 格子宽度
    $line_count    = count($datas); // 行数

    foreach ($datas as $idx => $line_datas) {
        $line_no           = $idx + 1; // 行号
        $line_prefix_width = ($line_count - $line_no) * $unit_width / 2; // 行前留白数

        echo str_repeat($space, $line_prefix_width);
        foreach ($line_datas as $num) {
            $num_width   = yanghui_num_width($num); // 数字宽度
            $left_width  = intval(($unit_width - $num_width) / 2); // 格子内左留白宽度
            $right_width = $unit_width - $left_width - $num_width; // 格子内右留白宽度

            echo str_repeat($space, $left_width);
            echo $num;
            echo str_repeat($space, $right_width);
        }
        echo $newline;
    }
}

// 获取最大数
function yanghui_max_num($datas) {
    $max = 0;
    foreach ($datas as $line_datas) {
        foreach ($line_datas as $num) {
            $max = max($max, $num);
        }   
    }   
    return $max;
}

// 获取数字的宽度
function yanghui_num_width($num) {
    return strlen(strval($num));
}

最后,过程的两部分组装起来,就是想要的结果。

function yanghui_dump($n) {
    $datas = yanghui_datas($n);
    yanghui_print($datas);
}

补充

有人说,杨辉三角中的数字,可以依据座标直接计算得到,没关系,替换掉获取数据的对应部分即可。

启示

当我们面对问题时,有两种思维模式:经验型和能力型。

经验型思维,搜索自己的知识库,发现杨辉三角应该要一个两层循环,就开始执行;执行了一步之后,发现卡住了,下一个问题自己的知识库中没有,然后就陷入了沉思!

能力型思维,先分析问题的本质构成,对问题进行分解,将复杂的大问题转换成多个简单的小问题,抽丝剥茧,在自己的知识库中搜索每个小问题的答案,最终解决问题。

两种思维模式的优劣势显而易见,经验型思维适合解决确定的、已知的、简单的问题,效率更高;能力型思维适合解决不确定的、未知的、复杂的问题,适应性更广。

通过程序设计锻炼能力型思维

编程,是一项极富创造力,注重逻辑和推演的工作。

当我们开始编程之前,首先需要站在具体问题的层面,对目标系统进行分析,然后,又需要站在抽象视角层面,把目标系统拔高一层进行演绎。在具体与抽象之间,不断的推敲,找到合理的对现实的抽象描述。

这个分析推演的过程,就是寻找事物本质的过程。只不过,我们的日常工作,面对大量繁杂的需求,没有过多的时间进行设计层面的思考。现实,把我们磨成了需求翻译机器。

大部分程序员其实不甘于此,所以,我想通过一份教程,帮助有意愿寻求改变的同学,促进这种改变的达成。

2014年,我编写了目前公司使用的框架,已经稳定运行超过5年,支撑了超过60万行业务代码,但它自身只有6000行代码。接下来一段时间,我会以它为蓝本,站在重新编写一个框架的角度上,全面的复现编写框架的思考、分析、设计、实现整个过程,并将其整理为一份视频教程,以期能帮助大家寻找到程序设计的感觉。(自愿付费,可自由分享与传播)

整个教程大致分12讲,每讲有一个独立的主题,每讲时间1-3小时不等,并配以一篇公众号文章发布。大的层面框架分为3个部分:与外部环境的交互、对业务层的封装约束、ORM之类的辅助工具。

教程

因文字表现力有限,本文仅作为一个引子,具体关于职业发展的分析、教程想要传递的价值、以及传递价值采用的方法,请下载视频教程深入学习。

【十年一剑,透过框架理解抽象思维】是我录制的一套系列教程,通过重现开发一套框架的思考、分析、设计、实现全过程,帮助大家寻找程序设计的灵感(自愿付费,可自由传播分享)。教程会通过公众号《抽象思维》发布。

今天发布的是第一讲《录制教程的意义》,感兴趣想要深入学习的同学,请扫码下载视频教程(下载地址: https://pan.baidu.com/wap/init?surl=IZ24Az5GFpQoQSD4WbfR8g 提取密码:rcga)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章