zoukankan      html  css  js  c++  java
  • 终端调测命令易用性的改进

    背景

         某平台向上层应用模块提供调测命令支持,其接口结构定义如下:

     1 #define   MAX_CMDMSG_LEN           32
     2 #define   MAX_ARG_NUM                  7
     3 #define   MAX_USERARG_NUM          (MAX_ARG_NUM - 1)
     4 
     5 typedef struct{
     6     INT32U arg_counts;                /* 二级进程调试命令带的参数个数 */
     7     CHAR  arg[MAX_USERARG_NUM][MAX_CMDMSG_LEN];   /* 调试命令的参数 */
     8 }CMD_USER_ARG;
     9 
    10 /* 用户处理命令函数*/
    11 typedef  INT32S (*CmdUserProcFun)(CMD_USER_ARG *pCmdUserArg);
    12 
    13 /* 应用模块处理命令数据结构 */
    14 typedef struct {
    15     INT32U     dwArgCounts;       /* 该命令实际带的参数 */
    16     INT32U     dwCheckArgCounts;  /* 统一比较的参数 */
    17     CHAR      *CmdStr[MAX_USERARG_NUM];
    18     CmdUserProcFun  procfun;
    19 }CMD_USER_PROC;
    Interface Structure

         某应用模块内部定义需要的命令列表,如下:

     1 static CMD_USER_PROC gOmciDebugMap[] = {
     2     {1, 1, {"help",0,0,0,0,0},           (CmdUserProcFun)OmciDebugHelp},
     3     {1, 1, {"showmectrl",0,0,0,0,0},     (CmdUserProcFun)ShowMeCtrl},
     4     {2, 1, {"showmeinfo",0,0,0,0,0},     (CmdUserProcFun)ShowMeInfo},
     5     {1, 1, {"showmeminfo",0,0,0,0,0},    (CmdUserProcFun)ShowMemInfo},
     6     {1, 1, {"showuniinfo",0,0,0,0,0},    (CmdUserProcFun)ShowUniInfo},
     7     {3, 1, {"setmectrl",0,0,0,0,0},      (CmdUserProcFun)SetMeCtrl},
     8     {2, 2, {"setmectrl","help",0,0,0,0}, (CmdUserProcFun)SetMeCtrlHelp},//注意此行!
     9     {3, 1, {"log",0,0,0,0,0},            (CmdUserProcFun)SetOmciLog},
    10     {4, 1, {"testbatch",0,0,0,0,0},      (CmdUserProcFun)TestBatch},
    11     {1, 1, {"testendian",0,0,0,0,0},     (CmdUserProcFun)TestEndianOper}
    12 };
    Command Lists

         对应的调测命令形如“sendcmd [pid] [module] [args...]”。本模块将解析处理args部分的参数。

         用户在终端(如串口)输入的模块调测命令,经平台捕获后转发给该模块。模块接收到平台发来的调测命令消息后,调用如下函数进行解析:

     1 VOID OmciDebugMsgProc(INT16U wEvent, VOID *lpMsg, INT16U wMsgLen)
     2 {
     3     INT32U dwDbgMapIdx = 0, dwChkArgIdx = 0;
     4     CMD_USER_ARG *ptCmdUserArg = (CMD_USER_ARG *)lpMsg;
     5 
     6     if((wMsgLen !=  sizeof(CMD_USER_ARG)) || (0 == ptCmdUserArg->arg_counts))
     7     {
     8         OmciDebugHelp(ptCmdUserArg);
     9         return ;
    10     }
    11 
    12     for(dwDbgMapIdx = 0; dwDbgMapIdx < (sizeof(gOmciDebugMap)/sizeof(CMD_USER_PROC)); dwDbgMapIdx++)
    13     {
    14         if(gOmciDebugMap[dwDbgMapIdx].dwArgCounts != ptCmdUserArg->arg_counts)
    15             continue;
    16 
    17         for(dwChkArgIdx = 0; dwChkArgIdx < gOmciDebugMap[dwDbgMapIdx].dwCheckArgCounts; dwChkArgIdx++)
    18         {
    19             if(0 != strcmp(ptCmdUserArg->arg[dwChkArgIdx], gOmciDebugMap[dwDbgMapIdx].CmdStr[dwChkArgIdx]))
    20             {   //若任一待检查命令参数不匹配,则认为整体不匹配
    21                 break;
    22             }
    23         }
    24 
    25         if(dwChkArgIdx == gOmciDebugMap[dwDbgMapIdx].dwCheckArgCounts)
    26         {
    27             gOmciDebugMap[dwDbgMapIdx].procfun(ptCmdUserArg); //调用相应的调试函数
    28             return ;
    29         }
    30     }
    31 
    32     OmciDebugHelp(ptCmdUserArg); //若未找到对应调试函数,则输出帮助信息
    33 }
    Command Parser

         可见,接收到的的命令必须满足以下条件才能正确解析:

    • 命令携带的参数数目与命令列表中某条目预期一致
    • 命令中待检查的参数与命令列表中某条目精确匹配(包括大小写)

         当命令解析失败时,调用全局帮助函数提示用户:

     1 INT32 OmciDebugHelp(CMD_USER_ARG * pCmdUserArg)
     2 {
     3     INT32U dwDbgMapIdx = 0, dwChkArgIdx = 0;
     4     CHAR szDbgBuf[MAX_CMDMSG_LEN] = {0};
     5 
     6     for(dwDbgMapIdx = 0; dwDbgMapIdx < (sizeof(gOmciDebugMap)/sizeof(CMD_USER_PROC)); dwDbgMapIdx++)
     7     {
     8         memset(szDbgBuf, 0, sizeof(szDbgBuf));
     9         for(dwChkArgIdx = 0; dwChkArgIdx < gOmciDebugMap[dwDbgMapIdx].dwCheckArgCounts; dwChkArgIdx++)
    10         {
    11             sprintf(szDbgBuf, "%s %s", szDbgBuf, gOmciDebugMap[dwDbgMapIdx].CmdStr[dwChkArgIdx]);
    12         }
    13         printf("sendcmd 132 omcidebug %s
    
    ", szDbgBuf);
    14     }
    15     return 0;
    16 }
    Global Helper

         因此,当用户输入错误的命令时,终端将呈现如下的“救命稻草” :

     1 sendcmd 132 omcidebug  help
     2 sendcmd 132 omcidebug  showmectrl
     3 sendcmd 132 omcidebug  showmeinfo
     4 sendcmd 132 omcidebug  showmeminfo
     5 sendcmd 132 omcidebug  showuniinfo
     6 sendcmd 132 omcidebug  setmectrl
     7 sendcmd 132 omcidebug  setmectrl help
     8 sendcmd 132 omcidebug  log
     9 sendcmd 132 omcidebug  testbatch
    10 sendcmd 132 omcidebug  testendian
    Helper Prompts

    问题

         问题是,这根稻草真的能“救命”吗?设想:

         1) 用户A从未接触过该模块调测命令,试探性地输入“sendcmd 132 omcidebug  help”(简记为“help”命令,后面类似)。

             令人惊讶的是,除了“setmectrl help”命令似乎可以进一步提供些有用信息外,其他的命令只有一个不太友好的组合字符串(“testendian”何解?)……

         2) 用户B对命令提示的组合字符串含义有所领悟,愉快地输入“showmeinfo”命令——What?终端又吐出一根稻草!

         3) 用户C曾亲睹旁人输入该模块调测命令,知道某些命令只提示了部分参数。于是自信地输入绝无隐藏参数的“showuninfo”命令,但是——

             为什么终端还是吐出一根稻草?!Are u kidding???

         至此,用户A、B、C全部败下阵来,内牛满面。所幸,因新手故,A还不至于显得太狼狈。

    改进

         经过缜密的调查,发现问题主要体现为:

    • 缺乏具体命令的帮助信息
    • 隐藏的参数未加以提示
    • 小写的组合字符串容易误读(用户C承认因老眼昏花将“showuniinfo”错看为“showuninfo”)

         于是乎,接下来对症下药。

         首先,将模块内部定义的命令列表稍加改造,如下:

     1 //调测命令表项宏,用于简化书写
     2 #define DEBUG_CMD_ENTRY(argNum, cmdStr, dbgFunc)   {argNum, 1, {cmdStr}, dbgFunc}
     3 #define DEBUG_CMD_ENTRY_END                        {0,      0, {NULL},   NULL}
     4 
     5 //调测命令表
     6 static CMD_USER_PROC gOmciDebugMap[] = {
     7     DEBUG_CMD_ENTRY(1, "Help",             OmciDebugHelp),
     8 
     9     DEBUG_CMD_ENTRY(1, "ShowMeCtrl",       ShowMeCtrl),
    10     DEBUG_CMD_ENTRY(2, "ShowMeInfo",       ShowMeInfo),
    11     DEBUG_CMD_ENTRY(1, "ShowMemInfo",      ShowMemInfo),
    12     DEBUG_CMD_ENTRY(1, "ShowUniInfo",      ShowUniInfo),
    13     DEBUG_CMD_ENTRY(3, "SetMeCtrl",        SetMeCtrl),
    14     DEBUG_CMD_ENTRY(3, "Log",              SetOmciLog),
    15     DEBUG_CMD_ENTRY(4, "TestBatch",        TestBatch),
    16     DEBUG_CMD_ENTRY(1, "TestEndian",       TestEndianOper),
    17 
    18     DEBUG_CMD_ENTRY_END
    19 };
    20 const INT32U OMCI_DEBUG_MAP_NUM = (INT32U)(sizeof(gOmciDebugMap)/sizeof(CMD_USER_PROC) - 1/*End*/);
    Improved Command Lists

         这里将“setmectrl help”与“setmectrl”命令合为一体,即命令帮助信息置于SetMeCtrl()函数内部*,不另设help函数。待检查参数定为单个字符串,且为PascalCase格式以增强可读性(“ShowUniInfo”比“showuniinfo”更易识别)。

         注*:若在全局帮助函数里提供各命令的详细帮助,则不利于代码的清晰化。

         接着,命令解析函数改为:

     1 /******************************************************************************
     2 * 函数名称:  IsUserNeedHelp
     3 * 功能说明:  判断用户是否需要调试帮助
     4              若首个待检查命令参数后紧跟"?"、"help"等字符串(可扩充),
     5              则认为用户期望查看调试命令内部帮助信息。
     6 * 输入参数:  VOID *pvDbgArg : 首个待检查命令参数指针
     7 * 输出参数:  NA
     8 * 返回值  :  BOOL
     9 ******************************************************************************/
    10 static BOOL IsUserNeedHelp(VOID *pvDbgArg)
    11 {
    12     return ((0 == strcmp(pvDbgArg, "?")) || (0 == strcmp(pvDbgArg, "help")));
    13 }
    14 
    15 
    16 VOID OmciDebugMsgProc(INT16U wEvent, VOID *lpMsg, INT16U wMsgLen)
    17 {
    18     INT32U dwDbgMapIdx = 0, dwChkArgIdx = 0;
    19     CMD_USER_ARG *ptCmdUserArg = (CMD_USER_ARG *)lpMsg;
    20 
    21     if((wMsgLen !=  sizeof(CMD_USER_ARG)) || (0 == ptCmdUserArg->arg_counts))
    22     {
    23         OmciDebugHelp(ptCmdUserArg);
    24         return ;
    25     }
    26 
    27     for(dwDbgMapIdx = 0; dwDbgMapIdx < OMCI_DEBUG_MAP_NUM; dwDbgMapIdx++)
    28     {
    29         if((gOmciDebugMap[dwDbgMapIdx].dwArgCounts != ptCmdUserArg->arg_counts) &&
    30            (0 == IsUserNeedHelp(ptCmdUserArg->arg[1])))
    31         {   //尽可能提供机会,以触发相应调试函数内部帮助信息
    32             continue;
    33         }
    34 
    35         for(dwChkArgIdx = 0; dwChkArgIdx < gOmciDebugMap[dwDbgMapIdx].dwCheckArgCounts; dwChkArgIdx++)
    36         {
    37             if(0 != strcasecmp(ptCmdUserArg->arg[dwChkArgIdx], gOmciDebugMap[dwDbgMapIdx].CmdStr[dwChkArgIdx]))
    38             {   //若任一待检查命令参数不匹配(不区分大小写),则认为整体不匹配
    39                 break;
    40             }
    41         }
    42 
    43         if(dwChkArgIdx == gOmciDebugMap[dwDbgMapIdx].dwCheckArgCounts)
    44         {
    45             gOmciDebugMap[dwDbgMapIdx].procfun(ptCmdUserArg); //调用相应的调试函数
    46             return ;
    47         }
    48     }
    49 
    50     OmciDebugHelp(ptCmdUserArg); //若未找到对应调试函数,则输出帮助信息
    51 }
    Improved Command Parser

         这样,用户在提示的参数后输入“?”或“help”,会显示命令的具体帮助信息。同时,因为不区分大小写**,用户可根据习惯输入全大写、全小写或PascalCase格式的参数。

         **:又不是账号密码,完全没必要区分大小写嘛~

         然后,全局帮助函数也稍作修改: 

     1 INT32 OmciDebugHelp(CMD_USER_ARG *pCmdUserArg)
     2 {
     3     INT32U dwDbgMapIdx = 0, dwChkArgIdx = 0;
     4     CHAR szDbgBuf[MAX_CMDMSG_LEN] = {0};
     5 
     6     printf("
    <Usage>: %s [Args...]. e.g.
    ", pszOmciDbgHead);
     7     for(dwDbgMapIdx = 0; dwDbgMapIdx < OMCI_DEBUG_MAP_NUM; dwDbgMapIdx++)
     8     {
     9         memset(szDbgBuf, 0, sizeof(szDbgBuf));
    10         for(dwChkArgIdx = 0; dwChkArgIdx < gOmciDebugMap[dwDbgMapIdx].dwCheckArgCounts; dwChkArgIdx++)
    11         {
    12             sprintf(szDbgBuf, "%s %s", szDbgBuf, gOmciDebugMap[dwDbgMapIdx].CmdStr[dwChkArgIdx]);
    13         }
    14         printf("%s %s%s
    ", pszOmciDbgHead, szDbgBuf,
    15                (gOmciDebugMap[dwDbgMapIdx].dwArgCounts>1) ? " ..." : "");
    16     }
    17 
    18     printf("<Note> [Args...] are case-insensative(i.e.ShowPwSrvInfo=showpwsrvinfo)!
    ");
    19     printf("       Press '?' or 'help' after the first [Arg] to get detailed usage.
    
    ");
    20 
    21     return 0;
    22 }
    Improved Global Helper

         经过改造后,终端呈现的帮助提示如下所示:

     1 <Usage>: sendcmd 132 omcidebug [Args...]. e.g.
     2 sendcmd 132 omcidebug  Help
     3 sendcmd 132 omcidebug  ShowMeCtrl
     4 sendcmd 132 omcidebug  ShowMeInfo ...
     5 sendcmd 132 omcidebug  ShowMemInfo
     6 sendcmd 132 omcidebug  ShowUniInfo
     7 sendcmd 132 omcidebug  SetMeCtrl ...
     8 sendcmd 132 omcidebug  Log ...
     9 sendcmd 132 omcidebug  TestBatch ...
    10 sendcmd 132 omcidebug  TestEndian
    11 <Note> [Args...] are case-insensative(i.e.ShowPwSrvInfo=showpwsrvinfo)!
    12        Press '?' or 'help' after the first [Arg] to get detailed usage.
    Improved Helper Prompts

         用户看到命令参数后携带“...”,就知道该命令有隐藏参数,再输入“?”或“help”即可获得具体的帮助信息。

         以“SetMeCtrl”命令为例,其处理函数实现如下:

     1 static INT32 SetMeCtrl(CMD_USER_ARG *pCmdUserArg)
     2 {
     3     if(IsUserNeedHelp(pCmdUserArg->arg[1]) ||
     4        IsUserNeedHelp(pCmdUserArg->arg[2]))
     5     {
     6         printf("
    <Usage>: %s SetMeCtrl [MeClass:Decimal][AutoCreated:BOOL]
    ", pszOmciDbgHead);
     7         printf("           Set MeClass=0 to Clear Me Create Ctrl File...
    
    ");
     8         return 0;
     9     }
    10 
    11     INT32U dwMeClass = 0;
    12     INT32U dwAutoCreated = OMCI_TRUE;
    13     StrToULong((CHAR *)pCmdUserArg->arg[1], &dwMeClass, 0);
    14     StrToULong((CHAR *)pCmdUserArg->arg[2], &dwAutoCreated, 0);
    15 
    16     dwAutoCreated = (OMCI_TRUE == dwAutoCreated);
    17     OmciSetMeCreateCtrl((INT16U)dwMeClass, (BOOL)dwAutoCreated);
    18     printf("
    ");
    19 
    20     OaSaveDbConfig();
    21     return 0;
    22 }
    Specific Command Handler

         至此,用户A、B或C可以一起愉快地“玩耍”了:

    1 sendcmd 132 omcidebug  SetMeCtrl ?
    2 <Usage>: sendcmd 132 omcidebug SetMeCtrl [MeClass:Decimal][AutoCreated:BOOL]
    3          Set MeClass=0 to Clear Me Create Ctrl File...
    Happy Ending
  • 相关阅读:
    什么造就一个伟大的站点
    我的一些关于商业计划书的经验
    iPhone开发:万能的NSData
    两种快速打乱NSMutableArray的方法
    交大校友:互联网大佬们
    程序员的十层楼 11层(上帝)
    iPhone开发:使用NSValue存储任意类型的数据
    Linux之lsof命令
    MySQL密码忘了怎么办?MySQL重置root密码方法
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/3745192.html
Copyright © 2011-2022 走看看