从解决 bug 谈到学习
本文篇幅较长,可以先收藏再看,我相信认真看完本文的,都会产生思维的碰撞,所以欢迎留言。
起因
下午的时候,社区里的一位朋友,问我怎么解决某个 bug 的问题,其实我觉得表面上看起来解决一个 bug,但更多的是怎么去思考,怎么去学习的问题。
先上几张聊天截图(有删减)
debug
问题: 想去除
Laravel MongoDB
拓展包,已经在config/app.php
删掉了Jenssegers\Mongodb\MongodbServiceProvider::class,
,依旧报错
这种情况其实经常会发生,解决思路一般先从这 3 步入手排除,如果不行,再考虑其他情况。
1、可能在 config/app.php
没有删掉对应的服务提供者
2、可能在你自己自定义的 ServiceProvider 中注册了第三方的服务提供者,没有删除或注释
3、可能你运行的时候编译缓存了(5.4 中移除了 optimize 命令全线OPcache ),没有执行php artisan clear-compiled
或者直接删掉 bootstrap\cache\services.php
其实从上述的可以延伸到几个知识点
拓展包的的服务提供者怎么注册,可以在哪些地方注册?
实际上无论上述的情况 1 还是情况 2 服务提供者归根到底都是有容器进行注册的
情况 1 在 config/app.php
中注册
例如通过情况 1 在 config/app.php
中的 providers
中定义的那些服务提供者,启动流程如下
请求应用的时候
首先在 public/index.php
中触发 handle
方法
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
接着,在 Illuminate/Foundation/Http/Kernel.php
中初始化各种服务提供者
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
...
}
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
...
$this->bootstrap();
...
}
/**
* Bootstrap the application for HTTP requests.
*
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
/**
* Get the bootstrap classes for the application.
*
* @return array
*/
protected function bootstrappers()
{
return $this->bootstrappers;
}
/**
* The bootstrap classes for the application.
*
* @var array
*/
protected $bootstrappers = [
...
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
...
];
而在 Illuminate/Foundation/Bootstrap/RegisterProviders.php
中开始注册配置中的提供者,也就是情况 1 中的config/app.php
里面的 providers
<?php
namespace Illuminate\Foundation\Bootstrap;
use Illuminate\Contracts\Foundation\Application;
class RegisterProviders
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
}
}
在 Illuminate/Foundation/Application.php
可以看到取的值是 $this->config['app.providers']
/**
* Register all of the configured providers.
*
* @return void
*/
public function registerConfiguredProviders()
{
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($this->config['app.providers']);
}
/**
* Get the path to the cached services.php file. 缓存文件的路径
*
* @return string
*/
public function getCachedServicesPath()
{
return $this->bootstrapPath().'/cache/services.php';
}
而在 Illuminate/Foundation/ProviderRepository.php
中
调用 app 的
register
方法进行注册,而这就是核心
/**
* Create a new service repository instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $manifestPath
* @return void
*/
public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
{
$this->app = $app;
$this->files = $files;
$this->manifestPath = $manifestPath;
}
/**
* Register the application service providers.
*
* @param array $providers
* @return void
*/
public function load(array $providers)
{
$manifest = $this->loadManifest();
// 加载编译的缓存文件,可以用来知道哪些提供者是延期加载的
if ($this->shouldRecompile($manifest, $providers)) {
$manifest = $this->compileManifest($providers);
}
// 接下来就是注册延期加载的提供者,当他符合条件,需要的时候,也会自动加载
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// 这里就是注册非延期加载的服务提供者,可以看到用到的还是 app 的 register方法。
foreach ($manifest['eager'] as $provider) {
$this->app->register($provider);
}
// 延期加载的就不在这里细说了
$this->app->addDeferredServices($manifest['deferred']);
}
/**
* Compile the application service manifest file.
*
* @param array $providers
* @return array
*/
protected function compileManifest($providers)
{
// 用来判断现在已有还是需要重新编译缓存文件
$manifest = $this->freshManifest($providers);
foreach ($providers as $provider) {
$instance = $this->createProvider($provider);
// 检查是否延迟加载的服务提供者
if ($instance->isDeferred()) {
foreach ($instance->provides() as $service) {
$manifest['deferred'][$service] = $provider;
}
$manifest['when'][$provider] = $instance->when();
}
// 如果不是延迟加载的就添加到缓存文件的 eager 数组中
else {
$manifest['eager'][] = $provider;
}
}
return $this->writeManifest($manifest);
}
/**
* Write the service manifest file to disk.
*
* @param array $manifest
* @return array
*/
public function writeManifest($manifest)
{
$this->files->put(
$this->manifestPath, '<?php return '.var_export($manifest, true).';'
);
return array_merge(['when' => []], $manifest);
}
/**
* Get the path to the cached services.php file. 缓存文件的路径
*
* @return string
*/
public function getCachedServicesPath()
{
return $this->bootstrapPath().'/cache/services.php';
}
以上就是 情况 1 在 config/app.php
注册的。可以总结到两点
1、会生成缓存文件,延迟加载的,保存在缓存文件的 deferred
数组中,非延迟加载的在缓存文件的 eager
数组中。缓存文件保存在 $this->bootstrapPath().'/cache/services.php'
也就是 bootstrap/cache/services.php
(细心的读者发现,聊天记录截图中存在笔误)
2、通过 app 的 register 注册
情况 2 在自定义服务提供者中注册
这个就不展开篇幅来讲具体过程了,例如这个 bug 重现,可能他在 app/Providers
目录下新建了一个 MongodbServiceProvider
类,在里面写了:
class MongodbServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
$this->app->register(Jenssegers\Mongodb\MongodbServiceProvider::class);
}
}
所以情况 2 还是一样的,1、生成缓存文件,2、通过 register 注册
情况 3 历史遗留问题
所以不难知道情况 3 ,可能其他都删掉或者注释掉,但编译的缓存文件没删除或更新。
从 debug 中看问题
其实从聊天截图中可以看出,我说清除一下缓存文件(原谅我这句话的确可能引起误解),他真的去执行了 php artisan cache:clear
但没有去执行我真正想表达的 php artisan clear-compiled
。
想想为什么,其实是因为学习的时候,心太浮了,或者说不会去类比,你学了 cache
清除的,你当时的想法可能就是
哦,我懂了,可以这样清除 cache
但你不会想到其他的是不是也可以类似清除 ,或者说压根没想过其他的还有编译出来的文件。
这其实就是学习的分水岭,当你学会 cache 可以清除的时候,不妨留个心眼看看 artisan 输出的命令
你就会发现哦,原来还有其他的,这时候就会让你去思考,这些都有什么用,什么时候会用到。有清除,那怎么生成。
学习为什么说一环扣一环,是因为你的思维是连贯的,而不是想文档是分散的。
如果他当时知道有这么一回事,那么他就不会无从下手,就不会不知道 debug 的方向。
debug 难,难在定位 bug 所在,而这又关乎于你平时是否多想多观察,没人敢说一看就知道是怎么回事,但却有人敢说,一看就可能是怎么回事,可以往这个方向去试试。这可以说是经验,但这经验不只是单纯踩坑多而积累的,而是平常多思考而积累的,也许有人没踩过这坑,但他想过这问题,他就知道应该往哪方面去尝试了。不要把所有的问题都推给 经验,其实没经验的只是你的思维,思维太幼稚了。你要做的不是书本让你学什么就学什么,而是你想学什么去找什么书
再谈学习
其实问题终究不会有什么大改变。羊毛出在羊身上。真的不是某些人的智商高,他学习快,而是他的思维。从一定程度上可以理解为情商。他知道什么是他想要的,什么是不想要的。跟这个妹子应该聊星座,跟那个妹子应该聊韩剧,而不是遇见妹子就聊
你知道世界上最好的语言是什么吗?PHP!
所以,我们先来知道什么叫学习。
在百度百科(先把你们的吐槽放放)中,是这么定义的
学习,是指通过阅读、听讲、思考、研究、实践等途径获得知识或技能的过程。
阅读、听讲,其实就是我们平常看文档,看书,看视频,以及任何一切我们能获取知识的途径。
很多不会学习的人,基本完成这两步就直接实践了。美其言曰:实践出真知。
其实这就是我不太赞成的一点,也是这些人学习吃力的地方。为什么这么说?且听我愚见:
1、首先,信息爆炸的时代,且不说吸收的知识好与坏,哪怕全是精华,你阅读,听讲,你只是停留在你知道有这么一回事的阶段罢了,你去实践,你会发现,你会有很多人的通病道理我都懂,臣妾做不到呀,你觉得自己好像 掌握 ,实际上你真的只是 了解 ,这中间真的是有差别的。不要企图问我怎样才算了解,怎样才算掌握。这问题真的只有你自己才知道。(实际很多人都觉得自己知道,实际上并不知道,哲学问题~哈哈)
打个比方,你知道吃饭手口并用呀,你也知道筷子,刀叉,这些餐具。但不代表你真的会使用筷子(至少我身边朋友一大半拿筷子的动作都是错的,一些小的菜就夹不起来),不代表你知道刀要怎么切,叉要怎么用。
这时候可能又有人跳出来说,我多尝试几种方式就好了嘛,总会学会的。
没错,是这样的,但你发现本质没有,在你多尝试几种方式的同时,你思维就在想这几种方式,而这些方式是通过你思考、研究得出来的,然后再加以实践修正。所以
关键的一步是思考、研究,重要的一步是实践修正
所以说了解,掌握,其实这些都不重要,取决于你的目标是什么罢了。重要的是从你获取到知识之后,有没有思考、研究,接着有没有实践修正,如果想学会真正学习,缺一不可。
当你真的会思考了,我相信你也会学习了。
所以我知道你们懒得总结,我帮你们总结一下
- 阅读、听讲只是你的敲门砖,让你知道有这个点罢了,让你知道还可以往这方面想而已。
- 思考、研究才是关键。你要明白你想学什么,而不是书本让你学什么。哲学来讲就是主观能动性。不然你看完,美其言曰“学完”,其实本质来讲根本就没学。
- 实践修正才是重要,无论你只能阅读听讲,还是你已经可以思考研究,都离不开实践,而实践在我看来只是为了去验证你的想法是否正确,让你总结出优与缺,总结出对与错,所以关键是什么:大胆去做,然后总结,接着思考,最后修正
学习,是人在生活过程中,通过获得经验而产生的行为或行为潜能的相对持久的行为方式。
其实学到老活到老就是这个理。为什么很多人看文档看不下去,学习学不下去,其实就是因为你觉得没有用、没意义、学得慢等等各种消极的因素影响。其实想过没有,归根到底,本质是因为,你没有获得经验,所以就没有了后面的转折句而产生的行为或行为潜能的相对持久
的行为方式
所以,举个例子,要明白一点,Laravel 文档都是最浅显的知识,因为作者需要给你成就感,所以作者写文档的时候循序渐进,由浅至深。但实际上当你真正学习完文档之后你就会发现,很多东西其实作者并没有提到,而这些点往往就是关键的点。例如为什么作者不写进文档,仅仅因为篇幅问题,还是写进去了之后,初学者在不懂本质的前提下会滥用,会产生其他的影响。所以每一份好的文档,其实都浓缩了作者的思维所在,要真正去学习,其实这些思维才是精华所在。
再比如,框架源码。很多人问,到底应不应该去学框架源码。我会反问她们,你学框架源码学什么,只是学他有哪些语法糖,是怎么实现的么?还是你想学作者的思维方式,数据结构,设计模式,新特性,哪怕往小里讲,作者的变量命名方式。所以这里其实回归到上面一个点不是框架让你学什么,而是你要从框架学什么。
搞清楚这点了自然而然的,你就会获得经验,你就可以继续转折句,相对持久的行为。
好了,讲到这里,就该讲讲为什么是相对持久了(坐稳了,开车了)。
学习一个东西,从浅至深,俗话说的好没有耕坏的田,只有累死的牛一样的,人的学习精力,是有限的,不应该什么都学。读书时候有句话伤其十指不如断其一指,也有句话闻道有先后,术业有专攻。你可能现在学某样知识 Z ,你觉得你有毅力有恒心,但实际情况是,当你学的越多,你就会发现你懂得越少,你就会自然而然的去学习其他的,不是说你放弃了 Z,而是你为了更好的学习 Z,你需要去学习 B,这时候你就能 ZB 了不是么。
或许你看完上面一段,你觉得矛盾,又叫我不应该什么都学,又叫我为了更好得装逼,先学会装,再学会逼。其实不矛盾的。正是因为你只是为了装逼,所以你不应该什么都学。你只要学会 ZB 就好。但同时由于你之前只学会 Z,所以你需要去学会 B。
总结
以上只是我个人的一些愚见罢了,一千个读者就有一千个哈姆雷特。觉得对你有用,取其精华去其糟粕,我倍感欣慰。觉得完全不同意我的观点,文明留言你的看法,让我也借鉴学习思考一下。
正因为有差异才组成缤纷多彩的世界,不是么?所以有你更精彩,不说了,情人节,你懂的。
本作品采用《CC 协议》,转载必须注明作者和本文链接
学习的艺术 :+1:
醍醐灌顶
挺不错的!当然每个领域具体学习方法不一样:有的需要借势造势,有的需要谋定而后动
拜读,受教了
不错,受教了
思维的碰撞,第三者的秘密
5.2用mongodb,几个月前在内网上折腾出来了。但是今天上线放到外网上的时候,连这个扩展都忘记怎么装。 人与人之间的差别啊真大。思维要迭代
可以进这个讨论群吗
好文!
学习==>思考==>疑惑==>解惑。学习引发思考,思考产生疑惑,有了疑惑就要去解决,解决了疑惑,从这个过程中获得乐趣与满足,这是学习最为内在的动力。
深受启发。赞