教程的问题
2022年3月8日,作者:Burke Holland,@burkeholland
编写一份优秀的教程并不容易。我很清楚这一点——我写过很多教程,并非每一篇都取得了巨大的成功。
事实证明,制作一份优秀的教程不在于你写了什么,而在于开发者是否能够在不必阅读每一个字的情况下取得成功。在本文中,我们将探讨开发容器如何减少用户可能遇到的错误,以及 Laravel PHP 项目 是如何在他们自己的教程中优雅地实现这一点并取得极佳效果的。
没人阅读
我们自己的关于如何在 Visual Studio Code 中使用 Dev Containers 的教程长期以来完成率一直很低——大约在 4% 到 6% 之间。

为了弄清楚人们是在哪里放弃的,我们进行了用户研究,并观察了人们尝试完成我们教程的过程。那真是……痛苦。
为什么人们无法完成教程原因显而易见:根本没人读它。人们直接跳过了说明,径直去看操作步骤。不可避免地,他们会卡住,因为他们犯了一个如果读了说明本不会犯的错误。
宾夕法尼亚州立大学教授 John M. Carroll 在他的开创性著作《纽伦堡漏斗:为实用计算机技能设计极简主义教学》中谈到了这一点。他写道:“[学习者]太忙于学习,以至于无法充分利用说明。这就是理解的悖论。”
我对此深有同感,你大概也是如此。当我阅读教程时,我的眼睛在四处扫视代码块,因为我正试图通过实践来学习。我真的太忙于学习,根本没空去读说明。
人们不会去读你的教程。或者至少不会像你希望的那样阅读得那么仔细。你能做的最好的事情,就是尽可能消除读者在学习过程中可能犯错的各个环节。实现这一点的方法之一,就是使用预配置的容器环境来完全省去任何环境设置步骤。
容器化开发环境
任何教程中的很大一部分通常都用于罗列一长串的前置条件和环境设置。我清晰地记得自己曾试图学习 Ruby on Rails,却花了大把时间试图在 Windows 上正确安装 Ruby——当时我还在纳闷“gem”到底是个什么东西,以及为什么它们不知怎么全都不见了。
容器化开发环境背后的理念是,你在 Docker 容器内部进行开发。这使得拥有一个完全便携、配置完整的开发环境成为可能,你可以随心所欲地启动或销毁它。然后,你就可以将这个环境作为一组配置文件交付给别人。
但是,你如何在容器内部进行开发呢?容器又没有一个图形界面让你直接启动 VS Code。
VS Code 的 Dev Containers 扩展正是为此而生。它既包含将 Docker 容器配置为开发环境的机制,又允许你从 VS Code 连接到该环境。它的实现方式是在容器内部安装一个小型服务器组件,你的本地 VS Code 与之进行通信。接下来,你的开发体验将与在本地开发完全一样,只不过 VS Code 附加到了容器环境,而不是你的本地环境。

为了创建一个容器化开发环境,通常你必须对 Docker 有所了解。很多人确实了解,但也有很多人并不了解(虽然你看不到我,但我正举着手呢),因此该扩展尽可能地将容器设置过程抽象化。我设置了一个新的 Python 容器。向导会引导你选择基础镜像和 Python 版本。然后,它允许你通过选择器列表向镜像添加其他软件。在这个例子中,我添加了 Azure CLI、Dotnet CLI 和 PowerShell……

这个过程会向项目中添加一个包含必要 Dockerfile 的 .devcontainer 文件夹。它还会添加一个 devcontainer.json 文件,该文件是定义开发环境各个方面(例如应安装哪些扩展、容器构建后应运行哪些设置命令等)的标准。由于你可以完全掌控环境及其设置,因此几乎可以自动化一切——包括依赖项安装、库版本等。
通过这种方式,你完全可以字面意义上地将一个完整、开箱即用的环境交到别人手中,而无需任何额外的设置步骤,也不会引发关于 Ruby gem 的生存危机。
有些团队已经在使用基于开发环境容器的方法,让用户能够快速上手原本非常复杂的环境。PHP 的 Laravel 框架就是一个很好的例子。
Laravel 解决方案
Laravel 是一个开源的 PHP MVC 框架。说它全面,是因为它还包含对象关系映射(ORM)、直接数据库访问、打包系统等功能。Laravel 能做的事情很多。为了体验它,你在上手时确实至少需要一个数据库。通常这会要求用户不仅安装 PHP,还要安装一个数据库——通常是 MySQL。当用户只是想简单试用一下你的框架时,这是一个不小的要求。
Laravel 通过容器化开发环境和一个名为 Sail 的工具解决了这个问题。要想从头开始搭建 Laravel、MySQL 服务器和 Redis 缓存,你只需运行一条命令……
curl -s "https://laravel.build/example-app?with=mysql,redis" | bash
这会创建一个带有 docker-compose 文件的新项目。该文件设置了三个容器——应用程序容器、MySQL 容器和 Redis 容器。你无需了解容器或这三个服务中的任何知识。Sail 为你抽象掉了这一切。然后,你执行 Sail 命令来启动环境……
./vendor/bin/sail up
示例应用程序直接跑起来了。无需安装 PHP。无需安装 Laravel。无需经历依赖解析步骤。直接就能成功。

我指定了我们的项目拥有 MySQL 服务器和 Redis 缓存,因此当项目启动时,我们实际上会得到三个容器。使用 VS Code 的 Docker 扩展可以看到它们。

这些容器通过网络连接在一起,以便我们能够从应用程序容器中调用 MySQL 或 Redis 缓存容器。
如果你将交互式终端连接到 sail-8.1/app container,你将在 /var/www/html 文件夹中看到你的项目。Docker 会将你本地机器上的项目“挂载”到容器中,因此你在开发过程中进行的任何更改,在刷新时都会实时反映在应用程序中。

添加 Dev Containers
现在也已经添加了对 Dev Containers 扩展的支持。要将适当的开发容器配置添加到此项目,你可以通过脚手架初始化相同项目并添加 &devcontainer 标志。
curl -s "https://laravel.build/example-app?with=mysql,redis&devcontainer" | bash
请注意,如果你想将 devcontainer 添加到现有的 Sail/Laravel 项目中,可以通过运行
php artisan sail:install --devcontainer来实现。
这会创建相同的项目配置,不过会包含一个 .devcontainer 文件夹。VS Code 会自动检测该文件夹,并提示你在容器中重新打开项目,从而跳过原本必需的 sail up 步骤。

VS Code 会附加到容器,因此你是在容器环境内部进行开发,而不是在本地环境中。你一眼就能看出来,因为 VS Code 左下角的远程连接指示器会告诉你这一点……

与在容器外部开发相比,在容器内部开发具有一些明显的优势。
开发上下文与应用上下文保持一致
当连接到容器时,你进行开发的上下文与应用程序运行的上下文是完全相同的。因此,你的终端就变成了容器的终端……

Dev Containers 扩展还为你提供了一个更全面的视角来了解当前的状态,例如哪些端口被转发了——以防你忘记你的应用程序是在哪里运行的。

Laravel 应用程序会自动启动,应用程序日志会被重定向输出到容器日志中。既然你大概想了解应用程序中正在发生什么,Dev Containers 扩展在 VS Code 中提供了一个新视图,你可以在其中查看所有运行中的容器,并连接以实时查看容器日志流。

自动化开发环境设置
极佳的开发者体验必然包含对编辑器的自定义。这包括编辑器本身的设置,以及任何需要添加到开箱即用体验中的扩展或其他支持。
对于 VS Code 和 Laravel,扩展是在 devcontainer.json 中建议的,但处于注释状态,因此不会自动安装。这使用户能够从一组已经确定好的扩展中进行选择,而不必费心去寻找配置编辑器的正确方法。
...
"extensions": [
// "mikestead.dotenv",
// "amiralizadeh9480.laravel-extra-intellisense",
// "ryannaddy.laravel-artisan",
// "onecentlin.laravel5-snippets",
// "onecentlin.laravel-blade"
],
少读多做
人们不怎么阅读。这其实也无可厚非。Laravel's 的教程不一定比其他教程短,但关键在于,如果你直接跳到代码部分并运行命令,它就能正常工作。开发容器让这一切成为了可能。现在,如果我们能知道如何为我们自己的使用 Docker 容器作为 Visual Studio Code 的开发环境教程制作一个开发容器,那就太好了……
祝编码愉快!
Burke Holland (@burkeholland)