https://mp.weixin.qq.com/s/sHitMzs8KXXO6joX8QqO_A?fbclid=IwAR1UGD7gemvKRY-IkOCEHGDru6YLDkI2vXojKpLTc-AZ4gN2Zz849YwJKZ8
SSLH 是一款采用 C 语言编写的开源端口复用软件,目前支持 HTTP、SSL、SSH、OpenVPN、Tinc、XMPP 等多种协议识别。它主要运行于 *nix 环境,源代码托管在 GitHub 上。
项目地址:https://github.com/yrutschle/sslh
更简单地说,SSLH 允许我们在 Linux 系统上的同一端口上运行多个程序/服务。因此,您可以用同一端口来同时使用两种服务。如果你遇到大多数端口被防火墙阻止的情况,SSLH 就可以帮你派上大用场。下面我们就来看一个 SSL 和 SSH 同时复用同一端口的实例。
[轉][Flutter]还在大量使用TextEditingController?试试这种解决方案
当用户输入大量信息时,通常会采用TextField列表来解决,这时如果需要对每个TextField进行控制或监听就需要大量的TextEditingController。
这里提供另一种思路,解决大量使用TextEditingController和信息整理困难的问题。
定义一个Map:
Map _userInfo;
这个Map作用于所有需要收集用户信息的地方。
使用TextField时这样操作:
这里全程未单独定义TextEditingController,只使用TextField默认的方法进行控制,且可以获得以下好处:
1.节省大量代码,简化业务逻辑。
2.用户输入完成时即得到一个包含所有信息的Map。
3.输入的内容不会丢失,只要这个Map不被销毁,下次进入(或返回)这个页面时所有内容都在,不用要求用户再次输入,提升用户体验。
第一次发文章,如有问题,欢迎指正。
作者:xSILENCEx
链接:https://juejin.im/post/5d649bfce51d453b1d648314
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这里提供另一种思路,解决大量使用TextEditingController和信息整理困难的问题。
定义一个Map:
Map
这个Map作用于所有需要收集用户信息的地方。
使用TextField时这样操作:
////邮箱
TextField(
controller: TextEditingController(text: _userInfo['account']),
onChanged: (value) {
_userInfo['account'] = value;
},
),
////昵称
TextField(
controller: TextEditingController(text: _userInfo['nickname']),
onChanged: (value) {
_userInfo['nickname'] = value;
},
),
////密码
TextField(
controller: TextEditingController(text: _userInfo['password']),
onChanged: (value) {
_userInfo['password'] = value;
},
),
这里全程未单独定义TextEditingController,只使用TextField默认的方法进行控制,且可以获得以下好处:
1.节省大量代码,简化业务逻辑。
2.用户输入完成时即得到一个包含所有信息的Map。
3.输入的内容不会丢失,只要这个Map不被销毁,下次进入(或返回)这个页面时所有内容都在,不用要求用户再次输入,提升用户体验。
第一次发文章,如有问题,欢迎指正。
作者:xSILENCEx
链接:https://juejin.im/post/5d649bfce51d453b1d648314
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
dart null list
減少null判斷情況
https://stackoverflow.com/questions/51036758/flutter-the-method-map-was-called-on-null
https://juejin.im/post/5c6d38b1f265da2dcc7feb69
https://cloud.tencent.com/developer/article/1370380
?? => if null
https://blog.csdn.net/ffa_ijj/article/details/85051156
這個判斷方式有個問題,當查詢回來為[],不是null情況下,在.first會出錯,因為 "No element"會出錯,但.toList()會正確,故改回
https://stackoverflow.com/questions/51036758/flutter-the-method-map-was-called-on-null
https://juejin.im/post/5c6d38b1f265da2dcc7feb69
https://cloud.tencent.com/developer/article/1370380
?? => if null
https://blog.csdn.net/ffa_ijj/article/details/85051156
return qres?.map((value) {
LinkedHashMap result = json.decode(value["jsonitem"]);
return result;
})?.first ?? new LinkedHashMap();
這個判斷方式有個問題,當查詢回來為[],不是null情況下,在.first會出錯,因為 "No element"會出錯,但.toList()會正確,故改回
if (qres == null || qres.isEmpty) return new Member(); // 待確定是否出錯
var resmap = qres.first;
return new Member.fromJson(json.decode(resmap["jsonitem"]));
git 不同專案使用不同帳號
git global 已設定,但其他專案需要設定其他的帳號
到專案目錄下的.git內,修改config檔案,補上
[user]
name = ooxx
email = ooxx@gmail.com
即可蓋過global
到專案目錄下的.git內,修改config檔案,補上
[user]
name = ooxx
email = ooxx@gmail.com
即可蓋過global
dart json serialisation dson
https://github.com/drails-dart/dart-serialise
https://github.com/parnham/dart-serialise
https://github.com/dart-league/dson
https://github.com/parnham/dart-serialise
https://github.com/dart-league/dson
json to dart flutter
https://javiercbk.github.io/json_to_dart/
https://ashamp.github.io/jsonToDartModel/
https://ashamp.github.io/jsonToDartModel/
[轉]Remix now force to use https, how can i continue to use http://remix.ethereum.org?
http://remix.ethereum.org
Not https://remix.ethereum.org
Not https://remix.ethereum.org
[轉]kubernetes高可用安装工具,一条命令,离线安装,包含所有依赖,内核负载不依赖haproxy keepalived,纯golang开发,99年证书,
https://github.com/fanux/sealos
[轉]datatable datatables nodejs
https://newcodingera.com/datatables-server-side-processing-using-nodejs-mysql/
https://1drv.ms/u/s!At2KEcnT9DVaiw88MAEXeML0bRf3
======================
datatables mongodb
https://github.com/deepikagunda/datatables
https://1drv.ms/u/s!At2KEcnT9DVaiw88MAEXeML0bRf3
Important! SqlInjection
======================
datatables mongodb
https://github.com/deepikagunda/datatables
[轉]secp256k1簽章的message,長度都必須是256 bits,也就是32 bytes
https://medium.com/@antonassocareer/web3-secp256k1-%E7%B0%BD%E7%AB%A0%E8%88%87solidity%E9%A9%97%E7%AB%A0-26ded518cfdc
那麼如果我們想要單純用私鑰簽章一段資料,不要有Ethereum定義的那些prefix的話,就必須要直接調用 secp256k1 這一包library了。不過在用之前要知道,所有要丟給secp256k1簽章的message,長度都必須是256 bits,也就是32 bytes。剛剛我們說web3的簽名函式丟什麼都可以,是因為它會幫我加上prefix之後再做sha3 Hash (keccak),最後一定會變成一個32 bytes的東西。如果我們自己純靠私要簽章訊息的話,也勢必要先通過這個函式來整理input長度
那麼如果我們想要單純用私鑰簽章一段資料,不要有Ethereum定義的那些prefix的話,就必須要直接調用 secp256k1 這一包library了。不過在用之前要知道,所有要丟給secp256k1簽章的message,長度都必須是256 bits,也就是32 bytes。剛剛我們說web3的簽名函式丟什麼都可以,是因為它會幫我加上prefix之後再做sha3 Hash (keccak),最後一定會變成一個32 bytes的東西。如果我們自己純靠私要簽章訊息的話,也勢必要先通過這個函式來整理input長度
sha3 Hash (keccak)
bip39 mnemonic bip32 seed ed25519 elliptic
const bip39 = require('bip39')
const bip32 = require('bip32');
const EC = require('elliptic').ec;
json =`test json file`
mnemonic = "簡 熙 夢 幾 聲 可 高 汪 煙 版 統 仇"
path = "m/2018'/5'/1'/0/1"
const sJWSinit = async () => {
console.log('-----sJWS Initial Start----- \n');
dkey = await DeriveKey(mnemonic, path)
console.log("\nGet dkey: %o \n", dkey)
console.log('\n-----elliptic ed25519 Start----- \n');
var EdDSA = require('elliptic').eddsa
var ec = new EdDSA('ed25519');
var eckeypair = ec.keyFromSecret(dkey.privateKey)
var privateKeyHex = new Buffer.from(eckeypair.getSecret()).toString('hex')
var publickeyHex = new Buffer.from(eckeypair.getPublic()).toString('hex')
console.log("private key hex: %o", privateKeyHex)
console.log("public key hex: %o\n", publickeyHex)
var signature = eckeypair.sign(json).toHex();
console.log("signature: %o\n", signature)
var ec2 = new EdDSA('ed25519');
var ec2keypair2 = ec2.keyFromPublic(publickeyHex, 'hex');
console.log("EdDSA json verify: %o", ec2keypair2.verify(json, signature));
}
(async () => {
console.log("--- aysnc sJWS init---")
await sJWSinit();
})();
async function DeriveKey(mnemonic, derivePath) {
if (bip39.validateMnemonic(mnemonic)) { console.log("mnemonic is fake!") }
return bip39.mnemonicToSeed(mnemonic).then((vseed)=>{
var root = bip32.fromSeed(vseed)
var PathNode = root.derivePath(derivePath)
console.log("# PATH 是 m/2018'/5'/1'/0/1/0 因為底下為derive(0),所以 path + '/0' \n")
console.log("privateKey (Hex): %o", PathNode.derive(0).privateKey.toString('hex'))
console.log("publicKey (Hex): %o", PathNode.derive(0).publicKey.toString('hex')) // 024ac10a81e3a0f86cb4dad68c6a26031d805a057f36048f80a5b91b1c2cb0588c 符合
return {
prv_buf: PathNode.derive(0).privateKey,
pub_buf: PathNode.derive(0).publicKey,
wif: PathNode.derive(0).toWIF(),
publicKey: PathNode.derive(0).publicKey.toString('hex'),
privateKey: PathNode.derive(0).privateKey.toString('hex'),
path: derivePath
}
}).catch((e) => {
console.error('handle error here: ', e.message)
})
}
bitcoinjs-lib HDNode.fromSeedBuffer error bip39 bip32 Address bitcoin ethereum
bitcoinSecp256r1.HDNode.fromSeedBuffer 無法使用,目前正確應該是用 bitcoinSecp256r1.bip32.fromSeed
jsrsasign 有異常
jsrsasign 有異常
const bip39 = require('bip39')
const bip32 = require('bip32');
const bitcoinSecp256r1 = require('bitcoinjs-lib')
const ethUtil = require('ethereumjs-util')
const EC = require('elliptic').ec;
// bitcoinSecp256r1.HDNode.fromSeedBuffer 無法使用,目前正確應該是用 bitcoinSecp256r1.bip32.fromSeed
mnemonic = "簡 熙 夢 幾 聲 可 高 汪 煙 版 統 仇"
path = "m/2018'/5'/1'/0/1"
type = "secp256r1"
// 驗證網頁 https://iancoleman.io/bip39/#chinese_traditional
if (bip39.validateMnemonic(mnemonic)) { console.log("mnemonic is fake!") }
const seed = bip39.mnemonicToSeed(mnemonic).then((vseed)=>{
var root = bip32.fromSeed(vseed)
var PathNode = root.derivePath(path)
console.log("---------------------------------------------")
console.log("# PATH 是 m/2018'/5'/1'/0/1/ \n")
console.log("Bitcoin Address: %o 符合 \n", getAddress(PathNode)) // 1GcgQJN7XgqkZkQcD4dzaZ7bjCFvQ6wxF2 符合 m/2018'/5'/1'/0/1
console.log("root toWIF: %o", root.toWIF())
console.log("PathNode toWIF: %o 符合", PathNode.toWIF()) // Kzq7FAYiWjDAcwU44FvcyCsCpJyLCD19n13FyQgLY6oBNajYcAYz 符合 m/2018'/5'/1'/0/1
console.log("--------------------------------------------- \n")
// 底下為derive(0),所以正確是 m/2018'/5'/1'/0/1/0 為 path + '/0'
console.log("---------------------------------------------")
console.log("# PATH 是 m/2018'/5'/1'/0/1/0 因為底下為derive(0),所以 path + '/0' \n")
console.log("privateKey (WIF): %o 符合", PathNode.derive(0).toWIF()) // L5ccMER4KyRn6pY6amvrFAHacpEsKrH1eTjDNeWwgXMnqjSCUU6N 符合
console.log("privateKey (Buffer): %o", PathNode.derive(0).privateKey)
console.log("privateKey (String): %o", PathNode.derive(0).privateKey.toString())
console.log("privateKey (Hex): %o", PathNode.derive(0).privateKey.toString('hex'))
console.log("privatekeyHex: %o \n", PathNode.derive(0).privkeyHex)
console.log("publicKey (Hex): %o 符合", PathNode.derive(0).publicKey.toString('hex')) //024ac10a81e3a0f86cb4dad68c6a26031d805a057f36048f80a5b91b1c2cb0588c 符合
console.log("Bitcoin Address: %o 符合", getAddress(PathNode.derive(0))) //1Gp8AuHiYyBixrvLkKtC4VDhxpvK8PmYEr 符合
console.log("--------------------------------------------- \n")
console.log('\n-----elliptic Initial Start----- \n');
var ec = new EC('p256');
let keyPair = ec.keyFromPrivate("83CFCC6EF1864C3303A5F8DEF2540167CB2DFA5DD22BB8D197B396972525FD56")
let pubKey = keyPair.getPublic();
console.log("pubKey: %o", pubKey)
// https://github.com/kjur/jsrsasign/issues/394
// sha512('aaa') => d6f644b19812e97b5d871658d6d3400ecd4787faeb9b8990c1e7608288664be77257104a58d033bcf1a0e0945ff06468ebe53e2dff36e248424c7273117dac09
let msgHash = 'd6f644b19812e97b5d871658d6d3400ecd4787faeb9b8990c1e7608288664be7'
let signatureBase64 = 'MEUCIBEcfv2o3UwqwV72CVuYi7HbjcoiuSQOULY5d+DuGt3UAiEAtoNrdNWvjfdz/vR6nPiD+RveKN5znBtYaIrRDp2K7Ks='
let signatureHex = Buffer.from(signatureBase64, 'base64').toString('hex');
let validSig = ec.verify(msgHash, signatureHex, pubKey);
console.log("Signature valid? %o \n", validSig);
// use json
var ec = new EC('secp256k1');
keyPair = ec.keyFromPrivate(dkey.publicKey)
pubKey = keyPair.getPublic();
console.log("pubKey: %o", pubKey)
var signature = keyPair.sign(json);
var derSign = signature.toDER();
//console.log("signature: %o", signature)
console.log("json verify: %o", keyPair.verify(json, derSign));
console.log('\n-----elliptic ed25519 Start----- \n');
var EdDSA = require('elliptic').eddsa
var ec2 = new EdDSA('ed25519');
var ec2keypair = ec2.keyFromSecret(dkey.privateKey)
//console.log("key: %o", key)
var signature = ec2keypair.sign(json).toHex();
console.log("signature: %o", signature)
var privateKeyHex = new Buffer(ec2keypair.getSecret()).toString('hex')
var publickeyHex = new Buffer(ec2keypair.getPublic()).toString('hex')
console.log("private key hex: %o", privateKeyHex)
console.log("public key hex: %o", publickeyHex)
var ec2keypair2 = ec2.keyFromPublic(publickeyHex, 'hex');
console.log("EdDSA json verify: %o", ec2keypair2.verify(json, signature));
bip39.mnemonicToSeed(mnemonic).then((vseed)=>{
var root = bitcoinSecp256r1.bip32.fromSeed(vseed)
var PathNode = root.derivePath(path)
console.log("bitcoinSecp256r1 privateKey (Hex): %o", PathNode.derive(0).privateKey.toString('hex'))
console.log("bitcoinSecp256r1 publicKey (Hex): %o", PathNode.derive(0).publicKey.toString('hex'))
const buf = Buffer.allocUnsafe(32);
new Buffer.from(msgHash).copy(buf, 0, 0, 32)
//msgbuf32 = new Buffer("01234567890123456789012345678901")
console.log("msgHash buf 32: %o", buf.toString("hex"))
var ecPair = bitcoinSecp256r1.ECPair.fromPrivateKey(PathNode.derive(0).privateKey)
var signstring = ecPair.sign(buf)
console.log("signstring: %o", signstring.toString("hex"))
var verifyresult = ecPair.verify(buf, signstring)
console.log("verify: %o", verifyresult)
})
})
DeriveKey(mnemonic, path, type).then((v)=>{
console.log("dkey: %o", v)
});
function getAddress (node, network) {
return bitcoinSecp256r1.payments.p2pkh({ pubkey: node.publicKey, network }).address
}
function getEthereumAddress(privkeyHex) {
const hexAddress = ethUtil.privateToAddress(Buffer.from(privkeyHex, 'hex')).toString('hex')
const checksumAddress = ethUtil.toChecksumAddress(hexAddress)
return checksumAddress
}
function DeriveKey(mnemonic, derivePath, type) {
switch (type) {
case "secp256r1":
if (bip39.validateMnemonic(mnemonic)) { console.log("mnemonic is fake!") }
return bip39.mnemonicToSeed(mnemonic).then((vseed)=>{
var root = bip32.fromSeed(vseed)
var PathNode = root.derivePath(derivePath)
console.log("# PATH 是 m/2018'/5'/1'/0/1/0 因為底下為derive(0),所以 path + '/0' \n")
console.log("privateKey (Hex): %o", PathNode.derive(0).privateKey.toString('hex'))
console.log("publicKey (Hex): %o 符合", PathNode.derive(0).publicKey.toString('hex')) // 024ac10a81e3a0f86cb4dad68c6a26031d805a057f36048f80a5b91b1c2cb0588c 符合
const buf = Buffer.allocUnsafe(32);
PathNode.derive(0).privateKey.copy(buf, 0, 0, 32)
console.log("Ethereum Address: %o 符合", getEthereumAddress(buf.toString('hex')) ) // 0xe020343a09086F53a203c9A0Ea76010049399575 符合
return {
pub_buf: PathNode.derive(0).publicKey,
wif: PathNode.derive(0).toWIF(),
publicKey: PathNode.derive(0).publicKey.toString('hex'),
privateKey: PathNode.derive(0).privateKey.toString('hex'),
ethAddress: getEthereumAddress(buf.toString('hex')),
path: derivePath
}
}).catch((e) => {
console.log('handle error here: ', e.message)
})
break;
default:
throw "type should be secp256k1 or secp256r1";
}
}
smart contract storage struct 未初始化 直接使用問題
https://github.com/knownsec/Ethereum-Smart-Contracts-Security-CheckList/blob/master/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6%E5%AE%A1%E8%AE%A1CheckList.md#11-%E6%9C%AA%E5%88%9D%E5%A7%8B%E5%8C%96%E7%9A%84%E5%82%A8%E5%AD%98%E6%8C%87%E9%92%88
https://www.chaindd.com/3102377.html
https://blog.b9lab.com/storage-pointers-in-solidity-7dcfaa536089
https://medium.com/loom-network/ethereum-solidity-memory-vs-storage-how-to-initialize-an-array-inside-a-struct-184baf6aa2eb
Use delete or new
https://www.chaindd.com/3102377.html
https://blog.b9lab.com/storage-pointers-in-solidity-7dcfaa536089
https://medium.com/loom-network/ethereum-solidity-memory-vs-storage-how-to-initialize-an-array-inside-a-struct-184baf6aa2eb
Use delete or new
web3 deploy smart contract 1.2.1
Use https://remix.ethereum.org Get Contract json and data.
In remix website, Compile finish. See Compliation Details.
1. ABI: click ABI buttion, get data. Use http://jsonviewer.stack.hu/ remove space
2. Compliation Details -> WEB3DEPLOY -> get data
3. cContract.options.from need put correct.
var Web3 = require("web3");
var provider = new Web3.providers.HttpProvider("http://ganache:8545");
var web3 = new Web3(provider);
//abi
var cContract = new web3.eth.Contract([{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}])
//bytecode
cContract.options.data = '0x608060405234801561001057600080fd5b5060bf8061001f6000396000f30060806040526004361060485763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166360fe47b18114604d5780636d4ce63c146064575b600080fd5b348015605857600080fd5b5060626004356088565b005b348015606f57600080fd5b506076608d565b60408051918252519081900360200190f35b600055565b600054905600a165627a7a72305820765480c908e5e28e3233e18bfa422944b42cad5fc08b77d7b22d3ddd7016a1380029'
cContract.options.from = '0xoooxxxoooxxxoooxxxoooxxxoooxxx'
cContract.options.gas = '4700000'
console.log("web3 version: %o", web3.version)
web3.eth.getAccounts().then(o=>{console.log(o)})
cContract.deploy().send()
.on('error', (error) => {
console.log("error: %o", error)
})
.on('transactionHash', (transactionHash) => {
console.log("transactionHash: %o", transactionHash)
})
.on('receipt', (receipt) => {
// receipt will contain deployed contract address
console.log("receipt: %o", receipt)
console.log("receipt.contractAddress: %o", receipt.contractAddress)
})
.on('confirmation', (confirmationNumber, receipt) => {
console.log("confirmationNumber: %o", confirmationNumber)
console.log("confirmation receipt: %o", receipt)
})
.then(function(newContractInstance){
if (typeof newContractInstance.options.address !== 'undefined') {
console.log('Contract mined! address: ' + newContractInstance.options.address);
}else{
console.log("newContractInstance.address is undefined!")
}
});
HDWalletProvider web3 smart contract connection need to stop or pool
HDWalletProvider engine.stop()
p = HDWalletProvider(MNEMONIC_SYSTEM, "http://ganache:8545", 0);
p.engine.stop();
HDWalletProvider engine.stop() and pool
export function GetHdProvider(id: number, rpcurl: string) {
//return new HDWalletProvider(MNEMONIC_SYSTEM, url, id);
return id==0?Provider1Pool.acquire():Provider2Pool.acquire();
}
// Normal use
const hdProvider0 = await GetHdProvider(0, providerUrl).then(function(client) {return client}).catch(function(err) {throw new HttpException(err.toString(), HttpStatus.BAD_REQUEST);});
const hdProvider1 = await GetHdProvider(1, providerUrl).then(function(client) {return client}).catch(function(err) {throw new HttpException(err.toString(), HttpStatus.BAD_REQUEST);});
// Normal release
ReleaseHDProvider1(hdProvider1 );
// Pool code
const genericPool = require("generic-pool");
const opts = {
max: 10, // maximum size of the pool
min: 2, // minimum size of the pool
idleTimeoutMillis: 30000,
log: true
};
const factory0 = { // maybe different parms
create: function() {
return new HDWalletProvider(MNEMONIC_SYSTEM, RPCURL, 0);
},
destroy: function(client) {
client.engine.stop();
}
};
const factory1 = { // maybe different parms
create: function() {
return new HDWalletProvider(MNEMONIC_SYSTEM, RPCURL, 1);
},
destroy: function(client) {
client.engine.stop();
}
};
export function ReleaseHDProvider1(client){
Provider1Pool.release(client);
console.log("Provider1Pool status: pool.size %o pool.available %o pool.pending %o", Provider1Pool.size, Provider1Pool.available, Provider1Pool.pending)
}
export function ReleaseHDProvider2(client){
Provider2Pool.release(client);
console.log("Provider2Pool status: pool.size %o pool.available %o pool.pending %o", Provider2Pool.size, Provider2Pool.available, Provider2Pool.pending)
}
const Provider1Pool = genericPool.createPool(factory0, opts);
const Provider2Pool = genericPool.createPool(factory1, opts);
Project專案
Triton Ho https://www.facebook.com/groups/616369245163622/permalink/1736421439825058/
今天不寫發大財的事,直接寫一下project planning的雜感好了。
——————————————————————————————————————————————————————
最兇險的專案不是那些deadline定得很趕的專案,而是那些沒有deadline的專案!
別以為「沒有deadline」是真的沒有deadline耶。
你想一下,跟你大大聲說「錢不是問題的人」,最終有120%是「反正我又沒錢,錢當然不是問題啊~」。
公司不是做慈善的,也不是給工程師來試新玩具的。所有的專案,最終還是要談C/P值。
(再次一句:工程就是來談C/P的,不談C/P請去當藝術家)
所謂「沒有deadline」,這很可能代表……
A) 你的主管現在忙別的專案管理,暫時沒空管這專案。
(然後等他有空回來看這專案時,就會問為何拖這麼久還結不了案了)
B) 你沒法拒絕其他人的需求改動。(背景聲:反正沒deadline,改一下需求去做得更好吧)
C) 你沒法有效限制專案的複雜性,大量的over-engineering。(背景聲:反正沒deadline,寫得好一點/多留一點空間,這樣子未來才容易維護嘛)
A+B+C的後果是……老闆過了三個月,突然有空來看看大家做什麼時,發現這專案已經過了二個月還沒有做完。
然後要求:都做了三個月了,現在再給你二星期時間來結案耶!
結果是:
要麼結不了案,投入大量心力的專案成為上不了市場的垃圾。
要麼是勉強能結案,但是有大量的over-engineering和一堆因為需求改了又改下不好的程式加構。
——————————————————————————————————————————————————————
雖然一堆入市未深的工程師整天會罵專案deadline為何定得那麼硬,一天都不能改……
但是嘛,先不談專案,我們談一下去旅行好了。
去旅行嘛,第一件事當然是跟公司申請休假,然後把信用卡丟給另一半叫她幫忙規劃(註:這是高度危險動作,好孩子絕對別學)。
http://xn--axxxx-fg1h292av9ap80aa49or2n76lzmxyz1bcr7i.com/,sxxxxxxxxx.com,txxxxxxxxxx.com上天天比價比優惠看旅行遊記的生活~
然後如果沒定下死線?應該我進棺材那一天還是在計劃行程中~
(謎之聲:某人站在你身後,她看起來很火……)
計劃行程的死線能不能改?當然不行,公司休假可是改不動啊!!!
回到公司專案,為何公司死線定得那麼硬,很多時候都是跟業務宣傳/合約罰款有關的。
你想像一下,一個手游專案要上市,當然不是先等程式完成後才慢慢宣傳衝人氣的。
一個遊戲要上市,當然是先定好上市日期,然後預先數月就要慢慢地製造話題,找人來做宣傳,一步一步炒熱氣氛。
然後人氣炒到最高點,玩家的期待度到達最大時,遊戲同步開賣大賺特賺那一波。
如果突然發現有bug,遊戲要延期一個月?
sorry囉,遊戲消費是不理性的,過了那一波熱潮,本來會付錢的玩家們早就付給另一個遊戲了。
讓話題多炒熱一個月去等bug先修好?
你以為是炒青菜,你想炒多久就多久嗎?
先不談熱度能否額外維持一個月還不消散。但是,高人流的宣傳管道,網上KOL,不是你想付錢就能立即買到的!
如果你沒法如期把遊戲上市,那麼你這個遊戲很可能就賠本賠很很大囉。
——————————————————————————————————————————————————————
專案deadline不能改,商業社會就是這樣子了。
追求要完美的,你應該去當藝術家不是當工程師的。
怎去在deadline前做完專案,固然跟你是否有留下足夠的buffering有關。
但是,這跟你的專案怎計劃也有很大關係的。
上古時期,有一堆人(CMMI)覺得,只要把文件寫得好,每一個專案把工序所用的時間都記錄下來。
然後你就能越來越變得成熟,能很精準地預估下一個專案所需時間了~
然後這些CMMI人大約會覺得:把台北的象山步道走100次,就能很精準地預估爬玉山攻項要多少時間了(笑~)
(香港版本:走城門水塘100次,就能預估蚺蛇尖攻項要多少時間了)
會行山的人都知道:
地圖/網上文章只能給你一個很基本的大概,一個路線最終要用多少時間/體力,你只能親自走一次才能答出來。
然後嘛……
很多表面上看起來相同的軟體專案,真正做下去時才發現是全新的未知領域……
——————————————————————————————————————————————————————
如果專案deadline不能改,那麼能改的就是:軟件的質素了。
以去旅行為例:
如果你有非常充份的時間,你買機票時大可以找不同銀行的信用卡優惠,看看飛行里數計劃,看看連同旅行一起訂的優惠……
如果沒時間,http://xn--skyxxxxxxx-rl5q.com/,輸入出發和回程時間,那一家最便宜就按下去算了。
一個能賣錢的軟體專案,正常應該可以再拆分為多個sub-task和milestone的。
重點:
首先開始做的,應該是最困難/你最沒法預估開發難度的工作。
A)
專案越早階段要改動上市日期,你能成功改動宣傳計劃的可能性就越高。
B)
越早發現專案進度不理想。之後比較容易的sub-task,你還是能以減少testcase coverage,刪掉不重要功能這些手段去追回進度。
如果你把困難而且必要的工作放在最後才做,那麼任何的預估錯誤就是專案延期囉~
(註1:一堆人覺得堅持一定要先寫testcase才能寫程式的……要麼他真的很幸福沒遇上過要衝死線的專案,要麼他活在童話世界……)
(註2:deadline前做不完的軟體功能嘛……如果不是關鍵性的,看看能不能當成bug再後補囉~)
——————————————————————————————————————————————————————
專案到底先做什麼:
我們以(重要/不重要),和(容易/困難)來給每一個sub-task排一下:
如果你明知deadline是完全不合理,你怎也沒可能把全部重要功能都做完,那麼就先做(重要+容易),在死線前能多做一個功能就多一個功能。
否則,先做(重要+困難)的。因為(重要+容易)的工作,常常是總有一點時間可以偷下來的。越近deadline,你越會珍惜你每一秒不做多餘的事。
重要工作全做完後,然後是(不重要+容易),在deadline前能做多少就多少。
(不重要+困難)工作嘛,讓他留在backlog算了。
——————————————————————————————————————————————————————
後記:
在只發了一篇文下,RDBMS課程普通票全賣光了
(謎之聲:你不是說五分鐘會搶光嗎?)
歡迎來買石虎愛心票耶XD
https://datasci.kktix.cc/events/rdbms20191005
另外,高流量雜感的淺談,定在10/10和10/11的早上9:30—12:00(二天內容相同)
請繼續耐心等候正式報名頁面……
今天不寫發大財的事,直接寫一下project planning的雜感好了。
——————————————————————————————————————————————————————
最兇險的專案不是那些deadline定得很趕的專案,而是那些沒有deadline的專案!
別以為「沒有deadline」是真的沒有deadline耶。
你想一下,跟你大大聲說「錢不是問題的人」,最終有120%是「反正我又沒錢,錢當然不是問題啊~」。
公司不是做慈善的,也不是給工程師來試新玩具的。所有的專案,最終還是要談C/P值。
(再次一句:工程就是來談C/P的,不談C/P請去當藝術家)
所謂「沒有deadline」,這很可能代表……
A) 你的主管現在忙別的專案管理,暫時沒空管這專案。
(然後等他有空回來看這專案時,就會問為何拖這麼久還結不了案了)
B) 你沒法拒絕其他人的需求改動。(背景聲:反正沒deadline,改一下需求去做得更好吧)
C) 你沒法有效限制專案的複雜性,大量的over-engineering。(背景聲:反正沒deadline,寫得好一點/多留一點空間,這樣子未來才容易維護嘛)
A+B+C的後果是……老闆過了三個月,突然有空來看看大家做什麼時,發現這專案已經過了二個月還沒有做完。
然後要求:都做了三個月了,現在再給你二星期時間來結案耶!
結果是:
要麼結不了案,投入大量心力的專案成為上不了市場的垃圾。
要麼是勉強能結案,但是有大量的over-engineering和一堆因為需求改了又改下不好的程式加構。
——————————————————————————————————————————————————————
雖然一堆入市未深的工程師整天會罵專案deadline為何定得那麼硬,一天都不能改……
但是嘛,先不談專案,我們談一下去旅行好了。
去旅行嘛,第一件事當然是跟公司申請休假,然後把信用卡丟給另一半叫她幫忙規劃(註:這是高度危險動作,好孩子絕對別學)。
http://xn--axxxx-fg1h292av9ap80aa49or2n76lzmxyz1bcr7i.com/,sxxxxxxxxx.com,txxxxxxxxxx.com上天天比價比優惠看旅行遊記的生活~
然後如果沒定下死線?應該我進棺材那一天還是在計劃行程中~
(謎之聲:某人站在你身後,她看起來很火……)
計劃行程的死線能不能改?當然不行,公司休假可是改不動啊!!!
回到公司專案,為何公司死線定得那麼硬,很多時候都是跟業務宣傳/合約罰款有關的。
你想像一下,一個手游專案要上市,當然不是先等程式完成後才慢慢宣傳衝人氣的。
一個遊戲要上市,當然是先定好上市日期,然後預先數月就要慢慢地製造話題,找人來做宣傳,一步一步炒熱氣氛。
然後人氣炒到最高點,玩家的期待度到達最大時,遊戲同步開賣大賺特賺那一波。
如果突然發現有bug,遊戲要延期一個月?
sorry囉,遊戲消費是不理性的,過了那一波熱潮,本來會付錢的玩家們早就付給另一個遊戲了。
讓話題多炒熱一個月去等bug先修好?
你以為是炒青菜,你想炒多久就多久嗎?
先不談熱度能否額外維持一個月還不消散。但是,高人流的宣傳管道,網上KOL,不是你想付錢就能立即買到的!
如果你沒法如期把遊戲上市,那麼你這個遊戲很可能就賠本賠很很大囉。
——————————————————————————————————————————————————————
專案deadline不能改,商業社會就是這樣子了。
追求要完美的,你應該去當藝術家不是當工程師的。
怎去在deadline前做完專案,固然跟你是否有留下足夠的buffering有關。
但是,這跟你的專案怎計劃也有很大關係的。
上古時期,有一堆人(CMMI)覺得,只要把文件寫得好,每一個專案把工序所用的時間都記錄下來。
然後你就能越來越變得成熟,能很精準地預估下一個專案所需時間了~
然後這些CMMI人大約會覺得:把台北的象山步道走100次,就能很精準地預估爬玉山攻項要多少時間了(笑~)
(香港版本:走城門水塘100次,就能預估蚺蛇尖攻項要多少時間了)
會行山的人都知道:
地圖/網上文章只能給你一個很基本的大概,一個路線最終要用多少時間/體力,你只能親自走一次才能答出來。
然後嘛……
很多表面上看起來相同的軟體專案,真正做下去時才發現是全新的未知領域……
——————————————————————————————————————————————————————
如果專案deadline不能改,那麼能改的就是:軟件的質素了。
以去旅行為例:
如果你有非常充份的時間,你買機票時大可以找不同銀行的信用卡優惠,看看飛行里數計劃,看看連同旅行一起訂的優惠……
如果沒時間,http://xn--skyxxxxxxx-rl5q.com/,輸入出發和回程時間,那一家最便宜就按下去算了。
一個能賣錢的軟體專案,正常應該可以再拆分為多個sub-task和milestone的。
重點:
首先開始做的,應該是最困難/你最沒法預估開發難度的工作。
A)
專案越早階段要改動上市日期,你能成功改動宣傳計劃的可能性就越高。
B)
越早發現專案進度不理想。之後比較容易的sub-task,你還是能以減少testcase coverage,刪掉不重要功能這些手段去追回進度。
如果你把困難而且必要的工作放在最後才做,那麼任何的預估錯誤就是專案延期囉~
(註1:一堆人覺得堅持一定要先寫testcase才能寫程式的……要麼他真的很幸福沒遇上過要衝死線的專案,要麼他活在童話世界……)
(註2:deadline前做不完的軟體功能嘛……如果不是關鍵性的,看看能不能當成bug再後補囉~)
——————————————————————————————————————————————————————
專案到底先做什麼:
我們以(重要/不重要),和(容易/困難)來給每一個sub-task排一下:
如果你明知deadline是完全不合理,你怎也沒可能把全部重要功能都做完,那麼就先做(重要+容易),在死線前能多做一個功能就多一個功能。
否則,先做(重要+困難)的。因為(重要+容易)的工作,常常是總有一點時間可以偷下來的。越近deadline,你越會珍惜你每一秒不做多餘的事。
重要工作全做完後,然後是(不重要+容易),在deadline前能做多少就多少。
(不重要+困難)工作嘛,讓他留在backlog算了。
——————————————————————————————————————————————————————
後記:
在只發了一篇文下,RDBMS課程普通票全賣光了
(謎之聲:你不是說五分鐘會搶光嗎?)
歡迎來買石虎愛心票耶XD
https://datasci.kktix.cc/events/rdbms20191005
另外,高流量雜感的淺談,定在10/10和10/11的早上9:30—12:00(二天內容相同)
請繼續耐心等候正式報名頁面……
HEX 0x string to []byte to string DecodeString
https://play.golang.org/p/i90_qsN2Sz-
https://onlineutf8tools.com/convert-hexadecimal-to-utf8
package main
import (
"fmt"
"encoding/hex"
)
func main() {
id := "0x1dd84569ad60faa1b6838526ba7156388cf7c8d376ed0ccda9bce0419c2c3519"
fmt.Printf("Ori ID: %v \n\n", id)
fmt.Printf("Ori ID[2:]: %v \n\n", id[2:])
byteid := []byte(id)
fmt.Printf("===== Byte id ===== Decimal \n")
fmt.Printf("Byte ID: %v \n", byteid)
fmt.Printf("Byte ID 0x%x \n\n", byteid)
fmt.Printf("===== Decode(Byte id[2:]) ===== Decimal HEX \n")
byteid = []byte(id[2:])
fmt.Printf("Byte ID: %v \n", byteid)
fmt.Printf("Byte ID 0x%x \n\n", byteid)
n, _ := hex.Decode(byteid, byteid)
fmt.Printf("Byte ID[2:]: %v \n", byteid)
fmt.Printf("Byte ID[2:] 0x%x \n", byteid)
fmt.Printf("Byte ID[2:] [:n]: %v \n", byteid[:n])
fmt.Printf("Byte ID[2:] [:n] 0x%x \n\n", byteid[:n])
fmt.Printf("===== id ===== Decimal \n")
idbyte32 := covertStringTByte32(id)
fmt.Printf("Byte32 ID: %v \n", idbyte32 )
//fmt.Printf("String ID: %s \n", idbyte32 )
fmt.Printf("HEX ID: 0x%x \n\n", idbyte32 )
fmt.Printf("===== id[2:] ===== Decimal \n")
idbyte32 = covertStringT2Byte32(id)
fmt.Printf("Byte32 ID: %v \n", idbyte32 )
//fmt.Printf("String ID: %s \n", idbyte32 )
fmt.Printf("HEX ID: 0x%x \n\n", idbyte32 )
fmt.Printf("===== DecodeString(id[2:]) ===== HEX \n")
idbyte32 = covertStringDecodeStringByte32(id)
fmt.Printf("Byte32 ID: %v \n", idbyte32 )
//fmt.Printf("String ID: %s \n", idbyte32 )
fmt.Printf("HEX ID: 0x%x \n", idbyte32 )
}
func covertStringTByte32(t string) [32]byte {
var b32 [32]byte
copy(b32[:], t)
return b32
}
func covertStringT2Byte32(t string) [32]byte {
var b32 [32]byte
copy(b32[:], t[2:]) //remove 0x
return b32
}
func covertStringDecodeStringByte32(t string) [32]byte {
data, err := hex.DecodeString(t[2:])
if err != nil {
fmt.Printf("ERR \n")
}
fmt.Printf("DecodeString data: %v \n", data)
fmt.Printf("DecodeString data length: %v \n\n", len(data))
var b32 [32]byte
copy(b32[:], data)
return b32
}
Ori ID: 0x1dd84569ad60faa1b6838526ba7156388cf7c8d376ed0ccda9bce0419c2c3519
Ori ID[2:]: 1dd84569ad60faa1b6838526ba7156388cf7c8d376ed0ccda9bce0419c2c3519
===== Byte id ===== Decimal
Byte ID: [48 120 49 100 100 56 52 53 54 57 97 100 54 48 102 97 97 49 98 54 56 51 56 53 50 54 98 97 55 49 53 54 51 56 56 99 102 55 99 56 100 51 55 54 101 100 48 99 99 100 97 57 98 99 101 48 52 49 57 99 50 99 51 53 49 57]
Byte ID 0x307831646438343536396164363066616131623638333835323662613731353633383863663763386433373665643063636461396263653034313963326333353139
===== Decode(Byte id[2:]) ===== Decimal HEX
Byte ID: [49 100 100 56 52 53 54 57 97 100 54 48 102 97 97 49 98 54 56 51 56 53 50 54 98 97 55 49 53 54 51 56 56 99 102 55 99 56 100 51 55 54 101 100 48 99 99 100 97 57 98 99 101 48 52 49 57 99 50 99 51 53 49 57]
Byte ID 0x31646438343536396164363066616131623638333835323662613731353633383863663763386433373665643063636461396263653034313963326333353139
Byte ID[2:]: [29 216 69 105 173 96 250 161 182 131 133 38 186 113 86 56 140 247 200 211 118 237 12 205 169 188 224 65 156 44 53 25 56 99 102 55 99 56 100 51 55 54 101 100 48 99 99 100 97 57 98 99 101 48 52 49 57 99 50 99 51 53 49 57]
Byte ID[2:] 0x1dd84569ad60faa1b6838526ba7156388cf7c8d376ed0ccda9bce0419c2c35193863663763386433373665643063636461396263653034313963326333353139
Byte ID[2:] [:n]: [29 216 69 105 173 96 250 161 182 131 133 38 186 113 86 56 140 247 200 211 118 237 12 205 169 188 224 65 156 44 53 25]
Byte ID[2:] [:n] 0x1dd84569ad60faa1b6838526ba7156388cf7c8d376ed0ccda9bce0419c2c3519
===== id ===== Decimal
Byte32 ID: [48 120 49 100 100 56 52 53 54 57 97 100 54 48 102 97 97 49 98 54 56 51 56 53 50 54 98 97 55 49 53 54]
HEX ID: 0x3078316464383435363961643630666161316236383338353236626137313536
===== id[2:] ===== Decimal
Byte32 ID: [49 100 100 56 52 53 54 57 97 100 54 48 102 97 97 49 98 54 56 51 56 53 50 54 98 97 55 49 53 54 51 56]
HEX ID: 0x3164643834353639616436306661613162363833383532366261373135363338
===== DecodeString(id[2:]) ===== HEX
DecodeString data: [29 216 69 105 173 96 250 161 182 131 133 38 186 113 86 56 140 247 200 211 118 237 12 205 169 188 224 65 156 44 53 25]
DecodeString data length: 32
Byte32 ID: [29 216 69 105 173 96 250 161 182 131 133 38 186 113 86 56 140 247 200 211 118 237 12 205 169 188 224 65 156 44 53 25]
HEX ID: 0x1dd84569ad60faa1b6838526ba7156388cf7c8d376ed0ccda9bce0419c2c3519
Program exited.
https://onlineutf8tools.com/convert-hexadecimal-to-utf8
訂閱:
文章 (Atom)