## 細節注意
一些重要但是容易被忽略的細節記錄。不起眼但很重要的知識。

家里的電器壞了,90%的原因是因為沒有插電源插頭。
寫代碼也是這樣,很多時候往往是因為不夠細心而出了問題,比如寫錯變量名、多了空格等,所以出現問題時最應該先細心的檢查一遍代碼在做調試。
*****
### 判斷細節
```php
if (0 == '') {
echo "output";
}
if (0 == 'str') {
echo "output";
}
if (0 == '1') {
echo "not output";
}
if (0 == '1str') {
echo "not output";
}
if ('0' == '') {
echo "not output";
}
if ('0' == 'str') {
echo "not output";
}
----
// 整形轉換實驗
var_dump((int) '01'); // 1
var_dump((int) '0 1'); // 0
var_dump((int) '0str'); // 0
var_dump((int) '0 str'); // 0
if (0 == '01') {
echo "no output";
}
if (0 == '0str') {
echo "output";
}
if (0 == '0 1') {
echo "output";
}
if (0 == '0 str') {
echo "output";
}
```
字符串 `'0'` 和整數 `0` 是不一樣的,在開發中要尤其注意這個容易忽略的細節。
字符串類型 與 整形 作比較時,字符串會自動轉換成 整形 在與之比較,**所以 開發時嚴格要求必須使用 全等比較,不要使用自動轉換的特性,除了提高性能外最重要的是能夠避免這種不易發現的安全問題。**
----
### 判斷盡量使用全等,不要讓其自動轉換布爾再比較
php7在線執行 http://www.dooccn.com/php7/
```php
var_dump((bool) 0); // false
var_dump((bool) -1); // true
var_dump((bool) '-1'); // true
var_dump((bool) '0'); // false
var_dump((bool) 'str'); // true
if (0 == '') {
echo 's'; // output
}
if (0 == 'str') {
echo 's'; // output
}
if ('0' == '') {
echo 's'; // no output
}
```
兩個類型不同的表達式進行比較時,會先各自自動換成布爾值后在進行比較,**只有當兩者類型不一致時才會發生類型自動轉換。**
>[tip] 當你要用比較時,永遠優先考慮使用全等式。
盡量只讓類型確定的表達式參與比較,類型不同時也可以手動轉換成一致類型后再全等比較,而不要依賴自動的布爾轉換,這樣做的原因不僅僅是因為自動轉換的性能消耗問題,更重要的是為了避免某些隱蔽的錯誤發生,使程序更加健壯。
>[tip] 糾正:上面說,類型不同的表達式比較時會自動轉換為布爾值,這是不完全正確的,應該為:和布爾值比較時 轉換為布爾值,和整形比較時,轉換為整形后再比較。
----
### php與js關于判斷的區別
```php
if ('string' == 0) {
echo "output";
}
```
js版本:比較上PHP和js是不同的,這有點出乎我的意料
javascript:
```javascript
var a = 'string'; a == 0;
// false
```
*****
### 使用不存在變量報錯的細節
使用不存在的變量會報錯,但有一種情況例外:
```php
echo $a; // 報錯 Notice: Undefined variable: a
$a = null;
echo $a['k']; // 不會報錯,值為null
或者
$a = false;
echo $a['k']; // 不會報錯,值為null
```
這個細節很重要,因為在嚴格框架下面,我們總會期望當使用不存在的變量時來拋出異常,如果業務邏輯依賴于此,就得小心了。
*****
#### MYSQL decimal(10,2) 四舍五入問題
```php
# number_format 也會四舍五入
echo number_format(12.088, 2, '.', ''); // 12.09
// 還指望mysql自動保留兩位小數,不進行任何四舍五入呢,沒想到在 MySQL5.5.53 版本下,會進行四舍五入(12.088 => 12.09),坑啊!看來任何時候都不要指望和依賴外部啊。(目前發現不論什么版本的MySQL 都會直接四舍五舍的)
// 金額格式化(保留兩位小數,四舍五舍)
function priceFormat($val)
{
// 保留兩位小數不四舍五入
return substr(sprintf("%.3f", $val), 0, -1);
}
```
>[danger] 任何時候都不要指望和依賴外部,所有數據直接算好,交給數據庫原原本本的存就可以,不要讓數據庫參與任何的邏輯部分,以及數據處理。
所以需要自己實現一個,但這也引發一個思考,那就是業務中到底需要保留多少位小數,以及如何對待多余的小數位。最簡單粗暴的方式就是 四舍五入 或 四舍五舍,不同交易場景下,這兩種方案都有不同的副作用,比如少收或多給。復雜的可能需要考慮銀行家算法。
*****
#### 浮點數計算問題
[php浮點數的精度問題深究 - php小松 - CSDN博客](https://blog.csdn.net/a454213722/article/details/52135462)
[PHP浮點數的一個常見問題的解答 | 風雪之隅](http://www.laruence.com/2013/03/26/2884.html)
[關于PHP浮點數你應該知道的(All 'bogus' about the float in PHP) | 風雪之隅](http://www.laruence.com/2011/12/19/2399.html)
[intval遇到小數為什么會減1-CSDN論壇](https://bbs.csdn.net/topics/390789758)
[PHP: BC 數學 函數 - Manual](https://www.php.net/manual/zh/ref.bc.php)
[PHP: GMP 函數 - Manual](https://www.php.net/manual/zh/ref.gmp.php)
```php
// 安全的數字計算方式
// https://www.cnblogs.com/phpfensi/p/8143367.html
// https://www.cnblogs.com/jiqing9006/p/5531687.html
// http://php.net/manual/zh/function.bcdiv.php
// https://blog.csdn.net/LJFPHP/article/details/82255389
// https://www.cnblogs.com/phpper/p/7664069.html
//$m和$n代表傳入的兩個數值,主要就是這兩個數值之間的比較
//$x代表傳入的方法,比如是;add,sub等
//$scale 代表傳入的小數點位數。這個根據需求更改即可
function calc($m, $n, $x = 'add', $scale = 2)
{
$errors = array(
'被除數不能為零',
'負數沒有平方根',
);
switch ($x) {
case 'add':
$t = bcadd($m, $n, $scale);
break;
case 'sub':
$t = bcsub($m, $n, $scale);
break;
case 'mul':
$t = bcmul($m, $n, $scale);
break;
case 'div':
if ($n != 0) {
$t = bcdiv($m, $n, $scale);
} else {
return $errors[0];
}
break;
case 'pow':
$t = bcpow($m, $n, $scale);
break;
case 'mod':
if ($n != 0) {
$t = bcmod($m, $n, $scale);
} else {
return $errors[0];
}
break;
case 'sqrt':
if ($m >= 0) {
$t = bcsqrt($m);
} else {
return $errors[1];
}
break;
}
return $t;
}
```
*****
#### tp $db->find(null) 注意
```php
Db::name('user')->find($userId);
```
如果 `$userId` 是 `null` ,那么 `find(null)` 會查詢出來表的第一條數據,由于我們的疏忽,沒有 `(int) $userId` ,很可能就造成業務邏輯不符合預期,甚至引起嚴重而隱秘的BUG。
突然感覺到參數類型嚴格限制語言的好處了,確實能在很大程度上幫助我們在開發時避免這類錯誤問題。
*****
### intval() 整形轉換問題
> 不管變量前面有多少個0,且數字都小于8,它會當作是八進制數轉換成十進制數
[php中intval()函數 - ann_glx - 博客園](https://www.cnblogs.com/anns/p/3494195.html)
*****
#### PHP intval() 處理大整形問題
可以使用:
```php
$paysn = floatval($_POST['paysn']);
```
不能在使用 `intval()`了
[php關于數字防注入,intval溢出,intval - u010412301的博客 - CSDN博客](http://blog.csdn.net/u010412301/article/details/55046733)
[PHP長整型在32位系統中強制轉化溢出 - CleverCode的博客 - CSDN博客](http://blog.csdn.net/clevercode/article/details/46423103)
[PHP-php使用intval長度超限的問題? - 德問:編程社交問答](http://www.dewen.net.cn/q/3969)
last update:2018-2-9 10:48:00
*****
### 條件判斷優先級問題之括號
```php
// 錯誤,不符合預期,且不易發現
if (!$info = $db->lock(true)->getRow($sql) || $info['s_patent_claim_status'] != 0) {}
// 正確,符合預期
if (!($info = $db->lock(true)->getRow($sql)) || $info['s_patent_claim_status'] != 0) {}
// 正確,符合預期
if ($patentId && $recordInfo = $db->lock(true)->getRow($sql)) {}
```
所以必須細心謹慎對待這類條件判斷問題,越不起眼越往往越容易出錯,要確保每個功能上線前都通過完備了測試。
*****
### mysql非嚴格模式下注意的細節
```
`status` tinyint(4) unsigned NOT NULL DEFAULT 0 COMMENT ''
```
unsigned 無符號的,在非嚴格模式下, `SET status = -1` 不會更改任何數據,也不會報錯,這時就需要注意了。
需要詳細的測試,程序要健壯,一定要在嚴格模式環境下進行開發,對返回進行檢查,及時發現問題。
*****
### var_export對標量不友好,特別是浮點型
```php
echo var_export(1.4, true); // 1.3999999999999999
echo var_export('1.4', true); // '1.4'
```
所以如果用到了var_export, 請判斷一下是否為標量:
```php
if (!is_scalar($value)) {
$val = var_export($value, true);
} else {
$val = $value;
}
```
*****
### @ 關鍵字慎用!!!
@ 屏蔽錯誤顯示,頁面出錯不會往下執行,但是頁面不會顯示任何報錯信息!這讓人很無語,如果不是確定的代碼,請不要使用@
不然沒有錯誤信息怎么調試呢,錯誤也很重要,要知道錯誤、BUG也是程序的一部分,也和程序本身一樣的同等重要,沒有錯誤信息、錯誤處理的程序是不完整的,是沒有靈魂的。
----
### 計算時注意數據類型是否為數字
這是js中的情況:
```javascript
1 + '9'
// "19"
```
php也有類似的問題,總之任何時候不要忘記,當你想要計算時,是否嚴格驗證了計算對象的數據類型。
----
### left join 左連一對多問題
a LEFT JOIN b ON a.id = b.mid
a INNER JOIN b ON a.id = b.mid
如果 a只有一條,但是對應的 b有兩條,**那么最終結果是兩條** ,這點容易讓人忽略掉
|id|name|
|---|---|
|10| name |
|id|mid|title|
|---|---|---|
|1|10| title1 |
|2|10| title2 |
>[tip] 如果右表 `ON` 外鍵字段有重復的,那么就會出現重復數據
~~~
1. select DISTINCT a.id, a.name, b.* from a left join b on a.id = b.mid 重復
2. select DISTINCT a.id, a.name from a left join b on a.id = b.mid 不重復
3. select a.id, a.name from a left join b on a.id = b.mid GROUP BY a.id 不重復
4. select group_concat(DISTINCT a.id) as id,a.name, b.* from a left join b on a.id = b.mid 不重復
https://blog.csdn.net/u010003835/article/details/79154457
DISTINCT 表示對后面的所有參數的拼接取 不重復的記錄,相當于 把 SELECT 表達式的項 拼接起來選唯一值。
即:行唯一,所以 上面 1 還是重復,2 不重復
~~~
[https://segmentfault.com/a/1190000017067294](https://segmentfault.com/a/1190000017067294)
[數據庫表連接的簡單解釋 - 阮一峰的網絡日志](http://www.ruanyifeng.com/blog/2019/01/table-join.html)
[數據庫的最簡單實現 - 阮一峰的網絡日志](http://www.ruanyifeng.com/blog/2014/07/database\_implementation.html)

ps: 待研究 on 和 where 的區別,對上面的情況來說理論上 where 寫 on 效果也是一樣的,甚至提前縮小了范圍表連接會更小
on a.id = b.mid where a.id = 1
表關系:條件兩邊都是表的字段,而不是其它值,如:a.id = b.mid
使用表連接,表關系不可無,并且 on 中只有寫 表關系 才有效,如果寫了 where 條件 會直接被忽略掉。
需要注意的是,表關系 也可以寫在 where 中,但是不建議這樣,應該都寫在 on 中提前縮小表連接范圍。
上面說法錯誤,正確如下:
1. 左聯或右連時 on 都不會縮小主表范圍,內聯可以縮小范圍。
2. on 只是查找副表數據與其連接,副表沒有數據不會影響結果(副表字段都是 null)。
3. where 是最終對數據行進行過濾
4. ~~on 上寫主表條件沒有作用(因為這個條件只是查找副表數據)~~,但可以寫副表 條件 縮小 副表范圍
5. ~~如果要過濾最終結果,只能依靠 where~~
6. where/join on 在 INNER/RIGHT 時沒什么區別,但是 LEFT 時就有很大區別:on 上只能過濾 副表的數據,并不能像 where 一樣 過濾最終數據,導致如果主表數據多,最終結果可能不能如愿。
~~~
join on 不同類型的字段比較時,如 int 與 var 比較時,會自動轉為 整形再比較,這就導致了 結果并不全等 如 1q 1_2 都是當做1來比較的
https://blog.csdn.net/u013378306/article/details/105110465/
~~~
----
### js篇:不要使用 “連相等賦值”
```javascript
function a() {
var b = d = 1;
}
a();
d; // 1
// d 成了全局的了,如果你想將d賦值給b,就不要這樣寫 “連相等賦值”,而是這樣:
function a() {
var d = 1, b = d;
}
a();
```
----
### mysql_insert_id() 受 insert 和update影響
所以 mysql_insert_id 不一定是取到 最后 insert 的id,如果中間 有update,則返回0 。
*****
### 注意隱含產生的引用
```php
$arr = [['a'], ['b']];
var_dump($arr);
foreach ($arr as &$item) {
foreach ($item as &$value) {
}
// unset($value);
}
// unset($item);
var_dump($arr);
```
```
array(2) {
[0]=>
array(1) {
[0]=>
string(1) "a"
}
[1]=>
array(1) {
[0]=>
string(1) "b"
}
}
array(2) {
[0]=>
array(1) {
[0]=>
string(1) "a"
}
[1]=>
&array(1) {
[0]=>
&string(1) "b"
}
}
```
----
### mysql 千萬不要使用 id != null
SELECT * FROM `sp_led_mould` WHERE id != null; 沒有結果
SELECT * FROM `sp_led_mould` WHERE id is not NULL; 才會有結果
mysql version: 5.6.16-log
----
### 注意不要 在 tp 同一模型上 上做多次更新
[更新 · ThinkPHP5.0完全開發手冊 · 看云](http://www.hmoore.net/manual/thinkphp5/135189)
> 注意不要在一個模型實例里面做多次更新,會導致部分重復數據不再更新,正確的方式應該是先查詢后更新或者使用模型類的`update`方法更新。
~~~
關于多次調用save更新只有第一次更新的數據成功,后面都不成功的,可以在調用save前調用->force()來強制更新,或者調用update更新但是不要調用where方法。調用force的原因是save更新后會$this->origin = $this->data;而更新時又會調用getChangedData檢查這個屬性,不調用force就會只更新上次沒更新過的字段,所以才會導致循環save只有第一條成功。模型的update就不會有這個問題,因為這個方法每次都是重新new一個實例
----
foreach(\[1,2,3\] as $k=>$v){
$user->save($data,\[id=>$v\]);
}
foreach更新只能更新第一條數據,什么原因?
~~~
----
### json_decode 問題
**浮點精度**
php.ini 建議配置:
~~~
serialize_precision: 16
serialize_precision: -1
~~~
否則會導致 json_encode 中浮點數出現精度問題
----
**大整形問題**
解決大整形數值會被轉成 科學計數法 string 類型:
```php
$res = json_decode($body, true, 512, JSON_BIGINT_AS_STRING);
```
----
last update: 2019-5-28 23:42:01
- 開始
- 公益
- 更好的使用看云
- 推薦書單
- 優秀資源整理
- 技術文章寫作規范
- SublimeText - 編碼利器
- PSR-0/PSR-4命名標準
- php的多進程實驗分析
- 高級PHP
- 進程
- 信號
- 事件
- IO模型
- 同步、異步
- socket
- Swoole
- PHP擴展
- Composer
- easyswoole
- php多線程
- 守護程序
- 文件鎖
- s-socket
- aphp
- 隊列&并發
- 隊列
- 講個故事
- 如何最大效率的問題
- 訪問式的web服務(一)
- 訪問式的web服務(二)
- 請求
- 瀏覽器訪問阻塞問題
- Swoole
- 你必須理解的計算機核心概念 - 碼農翻身
- CPU阿甘 - 碼農翻身
- 異步通知,那我要怎么通知你啊?
- 實時操作系統
- 深入實時 Linux
- Redis 實現隊列
- redis與隊列
- 定時-時鐘-阻塞
- 計算機的生命
- 多進程/多線程
- 進程通信
- 拜占庭將軍問題深入探討
- JAVA CAS原理深度分析
- 隊列的思考
- 走進并發的世界
- 鎖
- 事務筆記
- 并發問題帶來的后果
- 為什么說樂觀鎖是安全的
- 內存鎖與內存事務 - 劉小兵2014
- 加鎖還是不加鎖,這是一個問題 - 碼農翻身
- 編程世界的那把鎖 - 碼農翻身
- 如何保證萬無一失
- 傳統事務與柔性事務
- 大白話搞懂什么是同步/異步/阻塞/非阻塞
- redis實現鎖
- 淺談mysql事務
- PHP異常
- php錯誤
- 文件加載
- 路由與偽靜態
- URL模式之分析
- 字符串處理
- 正則表達式
- 數組合并與+
- 文件上傳
- 常用驗證與過濾
- 記錄
- 趣圖
- foreach需要注意的問題
- Discuz!筆記
- 程序設計思維
- 抽象與具體
- 配置
- 關于如何學習的思考
- 編程思維
- 談編程
- 如何安全的修改對象
- 臨時
- 臨時筆記
- 透過問題看本質
- 程序后門
- 邊界檢查
- session
- 安全
- 王垠
- 第三方數據接口
- 驗證碼問題
- 還是少不了虛擬機
- 程序員如何談戀愛
- 程序員為什么要一直改BUG,為什么不能一次性把代碼寫好?
- 碎碎念
- 算法
- 實用代碼
- 相對私密與絕對私密
- 學習目標
- 隨記
- 編程小知識
- foo
- 落盤
- URL編碼的思考
- 字符編碼
- Elasticsearch
- TCP-IP協議
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依賴注入
- 科目一
- 開發筆記
- 經緯度格式轉換
- php時區問題
- 解決本地開發時調用遠程AIP跨域問題
- 后期靜態綁定
- 談tp的跳轉提示頁面
- 無限分類問題
- 生成微縮圖
- MVC名詞
- MVC架構
- 也許模塊不是唯一的答案
- 哈希算法
- 開發后臺
- 軟件設計架構
- mysql表字段設計
- 上傳表如何設計
- 二開心得
- awesomes-tables
- 安全的代碼部署
- 微信開發筆記
- 賬戶授權相關
- 小程序獲取是否關注其公眾號
- 支付相關
- 提交訂單
- 微信支付筆記
- 支付接口筆記
- 支付中心開發
- 下單與支付
- 支付流程設計
- 訂單與支付設計
- 敏感操作驗證
- 排序設計
- 代碼的運行環境
- 搜索關鍵字的顯示處理
- 接口異步更新ip信息
- 圖片處理
- 項目搭建
- 閱讀文檔的新方式
- mysql_insert_id并發問題思考
- 行鎖注意事項
- 細節注意
- 如何處理用戶的輸入
- 不可見的字符
- 抽獎
- 時間處理
- 應用開發實戰
- python 學習記錄
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文檔相似度驗證
- thinkphp5.0數據庫與模型的研究
- workerman進程管理
- workerman網絡分析
- java學習記錄
- docker
- 筆記
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京東
- pc_detailpage_wareBusiness
- doc
- 電商網站設計
- iwebshop
- 商品規格分析
- 商品屬性分析
- tpshop
- 商品規格分析
- 商品屬性分析
- 電商表設計
- 設計記錄
- 優惠券
- 生成唯一訂單號
- 購物車技術
- 分類與類型
- 微信登錄與綁定
- 京東到家庫存系統架構設計
- crmeb
- 命名規范
- Nginx https配置
- 關于人工智能
- 從人的思考方式到二叉樹
- 架構
- 今日有感
- 文章保存
- 安全背后: 瀏覽器是如何校驗證書的
- 避不開的分布式事務
- devops自動化運維、部署、測試的最后一公里 —— ApiFox 云時代的接口管理工具
- 找到自己今生要做的事
- 自動化生活
- 開源與漿果
- Apifox: API 接口自動化測試指南