我对我们在软件工程就业市场中看到的趋势非常感兴趣。有时真的很难对正在发生的事情做出一个有凝聚力和准确的叙述,因为它发生得太快了,而且很少有人收集有关此事的数据。
例如,以下是一些我想知道答案的问题:
我汇总了过去 9 年 Stack Overflow 调查中关于开发人员就业的所有答案,以便开始能够回答其中一些问题。如果您对开发人员的工作前景感到好奇,希望这是有用的数据。
表格中的所有数字都代表了将自己确定为特定工作类型的调查对象的百分比。
SO 提出的问题多年来发生了变化,因此我们应该对此持保留态度。
我已经标准化了答案。例如,我将“开发人员、后端”和“后端 Web 开发人员”归为“后端”。
堆栈溢出允许人们在 2015 年之后选择 0 多个职位,在此之前,它们似乎仅限于一个。
在某些年份,开发人员有更多的工作选择可供选择。
我已经抛弃了很多题外话的工作类型,比如“企业服务开发人员”和“民选官员”。
这只是 Stack Overflow 数据,因此每年都会受到用户群规模的影响。
也就是说,让我们进入数据。我将删除我为创建聚合数据而编写的脚本以及本文底部的原始数据链接。
年 | 全栈 | 前端 | 后端 |
---|---|---|---|
2013 | 24.5 | 4.31 | 7.88 |
2014 | 25.72 | 5.02 | 9.3 |
2015 | 25.93 | 4.76 | 8.07 |
2016 年 | 37.88 | 5.13 | 10.82 |
2017 | 51.05 | 2.47 | 5.08 |
2018 | 44.87 | 35.22 | 53.92 |
2019 | 47.5 | 29.98 | 45.75 |
2020 | 42.08 | 28.38 | 42.24 |
2021 | 39.42 | 21.85 | 34.84 |
2022 | 39.17 | 21.72 | 36.99 |
2013 年,“前端”开发者与“全栈”开发者的比例为 15/85,而“后端”开发者与“全栈”开发者的比例为 24/76。
“更全栈,更少专业化”的趋势贯穿了整个十年的数据。值得注意的是,在 2017/2018 年,调查发生了巨大变化,这就是我们看到数字发生巨大变化的原因。
似乎可以合理地得出结论,过去十年的趋势是,只做前端工作或只做后端工作的开发人员比例正在下降。越来越多的人成为全栈工程师。
请记住,这也可能是多年来出现了更多的中小型公司。较小的公司通常需要更多的通才,但现在我只是猜测。
前端/后端工程师的比例保持相当稳定,后端工程师的数量略低于前端工程师的两倍。
这实际上让我感到惊讶,我预计这些年来前端和后端工程师的比例会越来越接近。
年 | 后端 | 开发运维 | 运维 |
---|---|---|---|
2013 | 7.88 | 0 | 2.96 |
2014 | 9.3 | 1.61 | 2.85 |
2015 | 8.07 | 1.23 | 1.77 |
2016 年 | 10.82 | 1.92 | 1.79 |
2017 | 5.08 | 7.81 | 13.66 |
2018 | 53.92 | 9.66 | 18.33 |
2019 | 45.75 | 11.43 | 15.93 |
2020 | 42.24 | 10.51 | 13.11 |
2021 | 34.84 | 9.75 | 10.82 |
2022 | 36.99 | 13.2 | 11.8 |
在我的脚本中,我尝试将更多“传统操作”角色拆分为“操作”类别,将“DevOps”内容拆分为“DevOps”角色。例如,我认为“SRE”是“DevOps”,而“系统管理员”是“ops”。
2017年到底发生了什么?老实说,数据似乎被破坏了。我手动挖掘数据,因为根据网站,他们声称 24% 的 Web 开发人员说他们是后端,75% 的受访者声称他们是 Web 开发人员。
目前,在我看来,他们一定对这些数字有一些预选赛,因为这并没有加起来。我将在解释中将 2017 年排除在我的分析之外。
DevOps 似乎正在超越传统运维。 2013 年,没有人认为自己是“DevOps”人,但到 2020 年和 2021 年,这些数字看起来非常相似。
值得指出的是,在 2016 年,DevOps 数字实际上在一年内超过了“ops”数字。我最好的猜测是,2016 年是许多公司开始简单地将他们的“运维”团队重新命名为“DevOps”团队以看起来很酷的时候。
很难过分关心这些数字,因为在我看来, DevOps 大多是错误的。我不相信大多数公司的“ops”头衔和“DevOps”头衔完全不同。
“DevOps”似乎是最近几年下降最少的,实际上在 2022 年有一个不错的增长。但是,如果你把“DevOps”和“ops”放在一起看,那么这个类别仍然有一点下降的趋势。
有趣的是,“运营”从一开始就呈下降趋势,而“后端”一直呈上升趋势,直到 2016 年趋势逆转,此后一直下降。
起初,我认为我们只是看到了在 Web 开发中看到的相同趋势:更多的通才,更少的专家。
然而,我开始怀疑,因为当我查看所有工作类别时,我注意到几乎所有工作类别都呈下降趋势......当我们查看百分比时显然不是这种情况 - 这是一个零和游戏。
我决定在我的脚本中添加一个新部分以进一步挖掘。我计算了每个调查对象平均声称有多少工作,并得到了以下数据:
年 | 后端 | 开发运维 | 运维 | avg_jobs_per_user |
---|---|---|---|---|
2013 | 7.88 | 0 | 2.96 | 1 |
2014 | 9.3 | 1.61 | 2.85 | 1 |
2015 | 8.07 | 1.23 | 1.77 | 1 |
2016 年 | 10.82 | 1.92 | 1.79 | 1.89 |
2017 | 5.08 | 7.81 | 13.66 | 2.48 |
2018 | 53.92 | 9.66 | 18.33 | 2.79 |
2019 | 45.75 | 11.43 | 15.93 | 2.84 |
2020 | 42.24 | 10.51 | 13.11 | 2.59 |
2021 | 34.84 | 9.75 | 10.82 | 2.21 |
2022 | 36.99 | 13.2 | 11.8 | 2.27 |
似乎从 2013 年到 2015 年,开发人员被限制只能提交一个答案,这有助于解释超低数字。然而,从 2019 年到 2021 年,每位用户的平均工作数量有所下降,这与“更通才”理论背道而驰。
还值得指出的是,随着岁月的流逝,Stack Overflow 实际上添加了更多专业类别,然后我冒昧地将其分组到这些更广泛的组中。
因此,实际上有充分的证据表明开发人员正在更加专业化,或者至少有更多可能的方式可以让人们专注于专业化。
也就是说,即使在查看了这些数据之后,我仍然认为后端开发人员将做越来越多的“DevOps”工作,尤其是在较小的公司。
年 | type_data_science | 数据工程师 | 后端 |
---|---|---|---|
2013 | 0 | 0 | 7.88 |
2014 | 0 | 0 | 9.3 |
2015 | 2.12 | 0.69 | 8.07 |
2016 年 | 3.83 | 0.7 | 10.82 |
2017 | 9.14 | 0 | 5.08 |
2018 | 7.17 | 0 | 53.92 |
2019 | 7.27 | 6.55 | 45.75 |
2020 | 6.19 | 5.8 | 42.24 |
2021 | 5.12 | 5 | 34.84 |
2022 | 4.67 | 4.91 | 36.99 |
对我来说非常有趣的是,数据工程真的只是在 2019 年才开始出现在调查数据中。在那之前,我猜后端工程师和数据科学家会承担这个角色。这种新的专业化当然很有趣。
机器学习在过去十年中绝对增长,但看起来 2017 年可能出现了一点“炒作泡沫”?
我已经谈到了我对我发现最有趣的数据的个人解释,但这里是我汇总的所有数据,因此您可以自己检查:
年 | avg_jobs_per_user | 全栈 | 前端 | 后端 | 开发运维 | 运维 | 移动的 | 桌面 | 嵌入式 | 数据科学 | 数据工程师 | 游戏 | 管理 | 质量保证 | 教育 | 设计 | 分析师 | 营销人员 | 忽视 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2013 | 1 | 24.5 | 4.31 | 7.88 | 0 | 2.96 | 6.48 | 9.53 | 2.16 | 0 | 0 | 0 | 7.49 | 0 | 0 | 0 | 0 | 0 | 34.7 |
2014 | 1 | 25.72 | 5.02 | 9.3 | 1.61 | 2.85 | 7.57 | 9.43 | 2.42 | 0 | 0 | 0 | 5.9 | 0 | 0 | 0 | 0 | 0 | 30.18 |
2015 | 1 | 25.93 | 4.76 | 8.07 | 1.23 | 1.77 | 7.28 | 6.65 | 2.33 | 2.12 | 0.69 | 0.63 | 1.44 | 0.63 | 0 | 0.57 | 0 | 0.23 | 35.66 |
2016 年 | 1.89 | 37.88 | 5.13 | 10.82 | 1.92 | 1.79 | 7.39 | 6.05 | 2.26 | 3.83 | 0.7 | 0.52 | 10.04 | 0.68 | 0 | 0.59 | 1.02 | 0.21 | 98.65 |
2017 | 2.48 | 51.05 | 2.47 | 5.08 | 7.81 | 13.66 | 16.2 | 20.3 | 6.52 | 9.14 | 0 | 3.37 | 0.5 | 2.44 | 1.42 | 3.94 | 3.69 | 0.3 | 100 |
2018 | 2.79 | 44.87 | 35.22 | 53.92 | 9.66 | 18.33 | 19.02 | 15.99 | 4.87 | 7.17 | 0 | 4.7 | 7.99 | 6.27 | 3.68 | 12.16 | 7.65 | 1.13 | 26.63 |
2019 | 2.84 | 47.5 | 29.98 | 45.75 | 11.43 | 15.93 | 16.54 | 19.48 | 8.15 | 7.27 | 6.55 | 4.99 | 6.34 | 7.15 | 10.18 | 10.33 | 7.08 | 1.1 | 28.37 |
2020 | 2.59 | 42.08 | 28.38 | 42.24 | 10.51 | 13.11 | 14.71 | 18.28 | 7.37 | 6.19 | 5.8 | 4.33 | 5.54 | 6.12 | 8.67 | 8.25 | 6.24 | 1 | 30.18 |
2021 | 2.21 | 39.42 | 21.85 | 34.84 | 9.75 | 10.82 | 11.74 | 13.23 | 5.51 | 5.12 | 5 | 2.53 | 6.37 | 4.33 | 5.56 | 5.53 | 4.54 | 0.76 | 34.51 |
2022 | 2.27 | 39.17 | 21.72 | 36.99 | 13.2 | 11.8 | 10.42 | 13.03 | 5.35 | 4.67 | 4.91 | 2.51 | 10.44 | 4.23 | 5.78 | 5.14 | 4.37 | 0.71 | 32.06 |
这是 Stack Overflow 上原始 CSV 数据的链接。
这是我用来处理数字的完整 Python 脚本。抱歉代码草率,我没有在代码中花费大量时间。
脚本中最有趣的部分可能是靠近底部的get_mapped_job
函数,在这里我将堆栈溢出用户报告的所有许多作业类型汇总到图表中包含的几个作业类型中。
import csv outpath = "csv/out.csv" type_devops = "devops" type_ops = "ops" type_backend = "backend" type_frontend = "frontend" type_mobile = "mobile" type_fullstack = "fullstack" type_desktop = "desktop" type_embedded = "embedded" type_data_science = "data_science" type_ignore = "ignore" type_management = "management" type_education = "education" type_design = "design" type_marketer = "marketer" type_data_engineer = "data_engineer" type_game = "game" type_analyst = "analyst" type_qa = "qa" def main(): files = [ (2013, [6]), (2014, [6]), (2015, [5]), (2016, [8, 9, 10]), (2017, [14, 15, 16, 17]), (2018, [9]), (2019, [12]), (2020, [13]), (2021, [11]), (2022, [11]), ] out_dict = {} jobs_per_user_dict = {} for f_tup in files: counts = {} path = f"csv/{f_tup[0]}.csv" print(f"generating report for {path}") out_dict[f_tup[0]]: {} with open(path, "r") as csvfile: rows = csv.reader(csvfile, delimiter=",") count = 0 rows_cpy = [] jobs_per_user = [] for row in rows: count += 1 rows_cpy.append(row) for row in rows_cpy: try: jobs = get_jobtext_from_cells(f_tup[1], row) mapped_jobs = set() for job in jobs: mapped_jobs.add(get_mapped_job(job)) jobs_per_user.append(mapped_jobs) for mapped_job in mapped_jobs: if mapped_job not in counts: counts[mapped_job] = 0 counts[mapped_job] += 1 except Exception as e: print(e) avg_jobs_per_user = 0 for user_jobs in jobs_per_user: avg_jobs_per_user += len(user_jobs) jobs_per_user_dict[f_tup[0]] = round( avg_jobs_per_user / len(jobs_per_user), 2 ) for job in counts: counts[job] /= count counts[job] *= 100 counts[job] = round(counts[job], 2) out_dict[f_tup[0]] = counts write_out(out_dict, jobs_per_user_dict) def get_jobtext_from_cells(indexes, row): if len(indexes) == 0: return [] job_texts = [] for i in indexes: cell = row[i] cell_job_texts = cell.split(";") job_texts += cell_job_texts return job_texts def write_out(out_dict, jobs_per_user_dict): types = [ type_fullstack, type_frontend, type_backend, type_devops, type_ops, type_mobile, type_desktop, type_embedded, type_data_science, type_data_engineer, type_game, type_management, type_qa, type_education, type_design, type_analyst, type_marketer, type_ignore, ] with open(outpath, "w") as csvfile: w = csv.writer(csvfile) w.writerow(["year", "avg_jobs_per_user"] + types) for year in out_dict: row = [year, jobs_per_user_dict[year]] for t in types: row.append(out_dict[year][t] if t in out_dict[year] else 0) w.writerow(row) def get_mapped_job(job): job = job.lower().strip() if job == "": return type_ignore if job == "devops specialist": return type_devops if job == "designer": return type_design if job == "c-suite executive": return type_management if job == "analyst or consultant": return type_analyst if job == "back-end developer": return type_backend if job == "windows phone": return type_mobile if job == "i don't work in tech": return type_ignore if job == "growth hacker": return type_marketer if job == "desktop developer": return type_desktop if job == "analyst": return type_analyst if job == "executive (vp of eng., cto, cio, etc.)": return type_management if job == "mobiledevelopertype": return type_mobile if job == "engineer, data": return type_data_engineer if job == "graphics programmer": return type_game if job == "systems administrator": return type_ops if job == "developer, game or graphics": return type_game if job == "desktop software developer": return type_desktop if job == "nondevelopertype": return type_ignore if job == "elected official": return type_ignore if job == "engineering manager": return type_management if job == "web developer": return type_fullstack if job == "machine learning specialist": return type_data_science if job == "data or business analyst": return type_analyst if job == "devtype": return type_fullstack if job == "response": return type_ignore if job == "developer, qa or test": return type_qa if job == "machine learning developer": return type_data_science if job == "developer, front-end": return type_frontend if job == "database administrator": return type_ops if job == "android": return type_mobile if job == "webdevelopertype": return type_fullstack if job == "blackberry": return type_mobile if job == "system administrator": return type_ops if job == "mobile developer - android": return type_mobile if job == "developertype": return type_fullstack if job == "ios": return type_mobile if job == "developer with a statistics or mathematics background": return type_ignore if job == "qa or test developer": return type_qa if job == "educator or academic researcher": return type_education if job == "engineer, site reliability": return type_devops if job == "marketing or sales professional": return type_marketer if job == "student": return type_ignore if job == "back-end web developer": return type_backend if job == "educator": return type_education if job == "front-end developer": return type_frontend if job == "developer, desktop or enterprise applications": return type_desktop if job == "senior executive/vp": return type_management if job == "occupation": return type_ignore if job == "scientist": return type_ignore if job == "developer, full-stack": return type_fullstack if job == "graphic designer": return type_design if job == "developer, embedded applications or devices": return type_embedded if job == "embedded application developer": return type_embedded if job == "quality assurance": return type_qa if job == "graphics programming": return type_game if job == "senior executive (c-suite, vp, etc.)": return type_management if job == "it staff / system administrator": return type_ops if job == "business intelligence or data warehousing expert": return type_data_engineer if job == "full stack web developer": return type_fullstack if job == "developer, mobile": return type_mobile if job == "front-end web developer": return type_frontend if job == "desktop applications developer": return type_desktop if job == "other (please specify):": return type_ignore if job == "mobile developer": return type_mobile if job == "devops": return type_devops if job == "enterprise level services developer": return type_ignore if job == "data scientist": return type_data_science if job == "executive (vp of eng, cto, cio, etc.)": return type_management if job == "mobile developer - ios": return type_mobile if job == "game or graphics developer": return type_game if job == "which of the following best describes your occupation?": return type_ignore if job == "other": return type_ignore if job == "desktop or enterprise applications developer": return type_desktop if job == "c-suite executive (ceo, cto, etc.)": return type_management if job == "embedded applications/devices developer": return type_embedded if job == "product manager": return type_ignore if job == "mobile application developer": return type_mobile if job == "mobile developer - windows phone": return type_mobile if job == "data scientist or machine learning specialist": return type_data_science if job == "educator or academic": return type_education if job == "embedded applications or devices developer": return type_embedded if job == "quality assurance engineer": return type_qa if job == "enterprise level services": return type_ignore if job == "full-stack developer": return type_fullstack if job == "na": return type_ignore if job == "academic researcher": return type_education if job == "manager of developers or team leader": return type_management if job == "marketing or sales manager": return type_marketer if job == "developer, back-end": return type_backend if job == "full-stack web developer": return type_fullstack if job == "designer or illustrator": return type_design if job == "programmer": return type_ignore if job == "developer": return type_ignore if job == "manager": return type_management if job == "engineer": return type_ignore if job == "sr. developer": return type_ignore if job == "full stack overflow developer": return type_fullstack if job == "ninja": return type_ignore if job == "mobile dev (android, ios, wp & multi-platform)": return type_mobile if job == "expert": return type_ignore if job == "rockstar": return type_ignore if job == "hacker": return type_ignore if job == "guru": return type_ignore if job == "self_identification": return type_ignore if job == "occupation_group": return type_ignore if job == "cloud infrastructure engineer": return type_devops if job == "project manager": return type_management if job == "security professional": return type_ops if job == "blockchain": return type_backend if ( job == "mathematics developers (data scientists, machine learning devs & devs with stats & math backgrounds)" ): return type_data_science raise Exception(f"job not mapped: {job}") main()
也在这里发布