چطور مثل یک حرفه‌ای برنامه بنویسیم – بخش دوم: حوزه‌ی متغیرها

در پست قبلی در مورد نحوه‌ی افزایش خوانایی برنامه با انتخاب نام مناسب برای متغیرها و تابع‌ها توضیح دادم. این موضوع در کلاس‌ها آکادمیک تدریس می‌شود و اکثر افراد با این موضوع آشنا هستند (هرچند که هنگام برنامه نوشتن این موضوع را رعایت نمی‌کنند). در این پست قصد دارم بیش‌تر در مورد متغیرها توضیح دهم. مطالبی که در این پست توضیح خواهم داد را معمولا نمی‌توان در بین مباحثی که در کلاس‌های آکادمیک تدریس می‌شود پیدا کرد. پس این پست را با دقت بخوانید.

هر متغیر در یک نقطه از برنامه تعریف می‌شود، در یک یا چند نقطه استفاده می‌شود و در یک نقطه نیز از بین می‌رود. از بین رفتن متغیر ممکن است به دلایل مختلفی رخ دهد. برای مثال در برخی از زبان‌های برنامه‌نویسی با استفاده از دستور free یا دستورهای مشابه می‌توان متغیر را به از بین برد. به جز این معمولا در زبان‌های برنامه‌نویسی وقتی که یک متغیر در یک تابع یا یک بلوک خاص از کد تعریف شده است، با رسیدن به انتهای بلوک متغیر نیز به صورت خودکار از بین می‌رود. با این حال، در این پست، ما دستوری که در آن برای آخرین بار از یک متغیر استفاده می‌شود را به عنوان نقطه‌ای که متغیر از بین می‌رود در نظر می‌گیریم. به فاصله‌ی بین جایی که یک متغیر تعریف می‌شود تا جایی که از بین می‌رود طول عمر متغیر گفته می‌شود. همچنین به فاصله‌ی بین دو استفاده‌ی متوالی از یک متغیر گام متغیر گفته می‌شود.

اجازه دهید با چند مثال موضوع را بیش‌تر توضیح دهم و چند تا از حالت‌های ممکن که یک متغیر می‌تواند از لحاظ طول عمر داشت باشد را بررسی کنیم. در تمام این مثال‌ها دستورها با مستطیل‌های توپر نشان داده‌ شده‌اند. پیکان‌هایی که در کنار این مستطیل‌ها نشان داده شده‌اند مشخص می‌کنند که دستور بعدی که از متغیر مورد نظر ما استفاده می‌کند کدام دستور است. در کنار هر شکل نیز یک کمان بزرگ نشان داده شده است که طول عمر متغیر مورد نظر ما را مشخص می‌کند.

این چند حالت را در نظر بگیرید. (این‌ها لزوما تمام حالت‌های ممکن نیستند)

حالت اول:‌ متغیر با طول عمر طولانی و گام‌های کوچک

در این مثال متغیر مورد نظر ما در اولین دستور تعریف شد است و در آخرین دستور از بین رفته است. بنابراین در این مثال، متغیر مورد نظر ما دارای عمر طولانی است، به این معنی که فاصله‌ی بین نقطه‌ای که تعریف شده است و نقطه‌ای که از بین رفته است زیاد است. اما در تمام طول این فاصله این متغیر استفاده شده است. پس گام‌های این متغیر کوتاه هستند. این حالت ممکن است در برنامه‌هایی که کارهای محاسباتی سنگین انجام ‌می‌دهند رخ دهد. با این حال روبه‌رو شدن با یک حالت این‌چنینی همیشه به این معنا نیست یک پردازش سنگین با استفاده از متغیر مورد نظر ما انجام می شود. گاهی برنامه‌نویس‌ها برای آن‌که (به خیال خود) در مصرف حافظه صرفه‌جویی کنند از یک متغیر برای ذخیره کردن چند مقدار استفاده می‌کنند. برای مثال یک برنامه‌نویس ممکن است که برای صرفه‌جویی در حافظه‌ی مصرفی برنامه‌ی خود یک متغیر رشته‌ای تعریف کند که برای سه منظور از آن استفاده می‌شود. در ابتدای برنامه از این متغیر برای ذخیره‌کردن نام کاربر استفاده می‌شود. در قسمت میانی برای ذخیره کردن یک پرس و جو که مد نظر کاربر است و در قسمت انتهایی برای ذخیره کردن پیام خروجی که قرار است به کاربر نشان دهد. برای مثال کد زیر را در نظر بگیرید.


String str;
str = getUsername();
// do something with username
// ...
str = "SELECT * FROM TABLE FOO";
// do something with the query
// ...
str = getErrorMessage();
// show error message on the screen
// ...

در تمام این کد از متغیر str به صورت مداوم استفاده شده است، ولی استفاده از یک متغیر به این صورت یکی از خطرناک‌ترین و آماتوری‌ترین روش‌های ممکن برای کد نویسی است. اگر جایی دیدید که یک برنامه‌نویس از یک متغیر به این صورت استفاده کرده است، بنا به دلایل زیر، مطمئن باشید که حتی الفبای برنامه‌نویسی را هم نمی‌داند.

  • امروزه هزینه‌ای که صرف پرداخت حقوق برنامه‌نویس‌ها می‌شود خیلی خیلی بیشتر از هزینه‌ای است که صرف خرید سخت‌افزار می‌شود. برای تمام سازمان‌ها و شرکت‌ها پرداخت کمی هزینه‌ی بیشتر برای خرید حافظه بسیار مقرون به صرفه‌تر از پرداخت دست‌مزد به برنامه‌نویسی است که وقت خود را صرف نوشتن یا فهمیدن یک کد این‌چنینی می‌کند.
  • دوباره یادآور می‌شود که نوشتن یک برنامه کار پیچیده‌ای است و استفاده از یک متغیر برای بیش از یک منظور فقط پیچیدگی برنامه را زیادتر می‌کند، چون کسی که در حال کار کردن روی کد است، علاوه بر حل کردن مساله‌ی اصلی، باید یک مساله‌ی اضافه‌ی دیگر را نیز حل کند. این فرد باید به خاطر بیاورد که در هر بخش از برنامه این متغیر برای چه منظوری استفاده شده است.
  • بر خلاف آنچه در ابتدا به نظر می‌رسد، در کامپایلرهای امروزی، استفاده از یک متغیر به جای سه ‌متغیر هیچ کمکی به صرفه‌جویی در حافظه نمی‌کند. کامپایلرهای امروزی بسیار هوشمند هستند و می‌توانند تشخیص دهند که از کدام خانه‌های حافظه در چه زمان‌هایی استفاده می‌شود و در چه زمان‌هایی استفاده نمی‌شود. این کامپایلرها به صورت خودکار خانه‌هایی از حافظه که قبلا به یک متغیر اختصاص داده شده بودند ولی دیگر از آن متغیر استفاده نمی‌شود را به متغیرهایی که در حال حاضر نیاز به حافظه دارند اختصاص می‌دهند. بنابراین در این مثال، شما چه یک متغیر برای هر سه منظور تعریف کنید و چه سه متغیر، فقط از یک خانه از حافظه برای ذخیره کردن آن‌ها استفاده می‌شود.

حالت دوم: متغیر با طول عمر طولانی و گام‌های متوسط

در این مثال متغیر دارای عمری طولانی است. ولی در تمام طول فاصله‌ای که متغیر معتبر است از آن استفاده نشده است. در این حالت احتمالا این بخش از برنامه دو کار را به صورت همزمان انجام می‌دهد. برای مثال احتمالا این بخش از برنامه یک سری محاسبه انجام می‌دهد و در کنار آن یک پیام به کاربر نشان می‌دهد که مشخص می‌کند چند درصد از محاسبات انجام شده است. اگر این موضوع صحت دارد، بهتر است که این بخش از برنامه به دو قسمت مجزا تقسیم شود و هر بخش فقط و فقط یک کار را انجام دهد.

حالت سوم: متغیر با طول عمر زیاد و گام‌های بلند

در این حالت متغیر دارای عمری طولانی است. ولی بعد از أنکه متغیر تعریف شد، مدت‌ها بدون استفاده باقی مانده است. در این مثال متغیر بعد از آن‌که در دستور اول تعریف شد تا دستور ششم دیگر مورد استفاده قرار نمی‌گیرد. بعد دوباره از این متغیر استفاده‌ای نمی‌شود تا آخرین دستور. این حالت یکی از بدترین حالت‌هایی است که ممکن است به وجود آید. زیرا از یک طرف فاصله‌ی محل تعریف و محل استفاده‌ی متغیر طولانی است و کسی که در حال کار کردن روی کد است نمی‌تواند هنگام دیدن کدی که از متغیر استفاده می‌کند، جایی که متغیر تعریف شده است را هم ببیند. ثانیا از آن‌جا که این متغیر به ندرت مورد استفاده قرار گرفته است، دلیل تعریف و استفاده از این متغیر در خاطر برنامه‌نویس باقی نمی‌ماند. اگر این متغیر بیش‌تر مورد استفاده قرار می‌گرفت، این احتمال وجود داشت که برنامه‌نویس ساده‌تر به یاد بیاورد که این متغیر چه جیزی را در خود ذخیره می‌کند. به هر حال می‌توان با تعریف کردن متغیر در جایی نزدیک به اولین جایی که متغیر استفاده شده است تا حدی از این مشکل کاست.

حالت چهارم: متغیر با طول عمر طولانی، یک گام بلند و سپس گام‌های کوچک

در این مثال هم متغیر دارای عمر طولانی است، اما این متغیر فقط در بخش کوچکی از برنامه استفاده شده است (در چند دستور آخر). بین جایی که متغیر تعریف شده است و جایی که برای اولین بار از آن استفاده شده است یک فاصله‌ی بزرگ وجود دارد. با تعریف کردن متغیر در نزدیکی اولین جایی که از آن استفاده شده است می‌توان مشکل این حالت را برطرف کرد.

حالت پنجم:‌ متغیر با طول عمر کوتاه و گام‌های کوچک

این یک مثال از متغیری است که طول عمر کوتاهی دارد. این متغیر نزدیک به جایی که برای بار اول از أن استفاده می‌شود تعریف شده است. این حالت بهترین حالت تعریف و استفاده از یک متغیر است. در این حالت وقتی برنامه‌نویس در حال کار کردن روی کد است، می‌تواند به طور همزمان جایی که متغیر تعریف شده و تمام استفاده‌هایی که از این متغیر شده است را ببیند.

حالا که توضیح طول عمر متغیرها و اندازه‌ی گام‌های آن‌ها تمام شد بهتر است به کدی که بحث روی آن بود برگردیم.

در کد برنامه‌ای که در پست قبلی مورد بحث بود و در انتهای این پست هم آمده است، یک متغیر به نام temp تعریف شده است (از بین تمام متغیرهای این برنامه به عمد این متغیر را انتخاب کرده‌ام چون temp یکی از بدترین اسم‌هایی است که می‌توان برای یک متغیر انتخاب کرد). این متغیر در ابتدای برنامه تعریف شده است و فقط و فقط در تابع compute که در انتهای برنامه قرار دارد استفاده شده است. به این ترتیب طول عمر این متغیر بسیار زیاد است. من در یک مانیتور ۱۹ اینچ واید نتوانستم همزمان وقتی که در حال خواندن کد تابع compute بودم، جایی که این متغیر تعریف شده بود را هم ببینم. برای دیدن جایی که این متغیر تعریف شده است و توضیحاتی که در مورد این متغیر نوشته شده است مجبور شدم که چند بار کد را اسکرول کنم. این کار فقط باعث شد که رشته‌ی افکار من که روی تابع compute متمرکز شده بود گسسته شود و معطوف جست‌وجو در کد و پیدا کردن متغیر compute شود. لازم است دوباره این مطلب را یادآوری کنم که نوشتن و فهمیدن یک برنامه‌ی این‌چنینی به اندازه‌ی کافی کار پیچیده‌ای است و برهم زدن تمرکز برنامه‌نویسی که در حال کار کردن روی کد است، به هر دلیل، فقط این پیچیدگی را بیش‌تر می‌کند. شخصی که این کد را نوشته باید دلیل بسیار خوبی برای عمومی تعریف نکردن این متغیر داشته باشد زیرا از نظر من هیچ دلیلی وجود ندارد که این متغیر را به صورت محلی در تابع compute تعریف نکرد.

متاسفانه این مطلب در مورد سایر متغیرهای عمومی که در این برنامه تعریف شده‌اند نیز صدق می‌کند. تمام این متغیر‌ها فقط و فقط در بخش کوچک از کد (هر کدام فقط در یک تابع) استفاده شده‌اند.

در صورتی که متغیر sub در برنامه‌ی زیر در کنار جایی که از آن استفاده شده است (داخل تابع compute) تعریف شده بود، برنامه‌نویس اصلی این کد احتمالا خیلی ساده‌تر می‌توانست ببیند که sub یک ساختمان داده است که روی آن فقط سه عمل contains‌ و add و clear انجام می‌شود و شاید برای آن‌که کارآیی کد خود را بهتر کند به جای آنکه sub را از نوع ArrayList تعریف می‌کرد، آن‌را از نوع HashSet تعریف می‌کرد تا دو عمل contains‌ و add با سرعت بسیار بیشتری (۱)o روی آن اجرا شوند.


پاورقی

برای تولید شکل‌ها از نرم‌افزار asymptote همراه با نرم‌افزار ImageMagick استفاده شده است.


کد اصلی

/*
/* this class implements the encoding requested in problem #1706
*/
/**
* @author Esmaeil Ashrafi <s.ashrafi@gmail.com>
*/
import java.util.ArrayList;
public class StierlitzCipher {
private static String input; // the string suppose to be ciphered
private static int k; // the length of sub strings
// temporary variable for query substrings
private static StringBuffer temp = new StringBuffer();
// an array to store substrings of each query string
private static ArrayList<String> sub = new ArrayList<String>();
/**
* @param args
* the command line arguments
*/
public static void main(final String[] args) {
/*
* if ran in command line console,first argument willl be the string to
* cipher and second considered as the key
*/
if (args.length == 2) {
input = new String(args[1]);
k = Integer.parseInt(args[0]);
} // otherwise use of default values of problem
else {
input = new String("abaccc");
k = 3;
// prompt the user to using default values
System.out.println("doing encrypt by default values:"
+ "\nString : \"abaccc\"\nKey : 3");
}
query(input, k);
System.out.println(); // just for trimming the output !
}// end of main
/**
* gets the entire string and splits it to substrings in length of "len" and
* sends each substring to method compute for calculating the number of
* different non-empty substrings could obtained from them.there will be
* substrings(and consequent call to compute method)call as much as the length
* of parameter str
*
* @param str
* - the string to be cipher
* @param len
* - the length of substrings(the key)
*/
private static void query(final String str, final int len) {
int m;
final int strLen = str.length();
for (m = 0; m <= strLen - len; m++) {
compute(str.substring(m, m + len));
}
for (m = m; m < strLen; m++) {
compute(str.substring(m).concat(str.substring(0, len - (strLen - m))));
}
}// end of query
/**
* queries queryString to calculate number of its different and non-empty
* substrings and prints the count to the standard output
*
* @param queryStr
* - the string to be queried
*/
private static void compute(final String queryStr) {
final int qLen = queryStr.length();
for (int i = 0; i < qLen; i++) {
for (int j = i + 1; j <= qLen; j++) {
if (!sub.contains(temp.replace(0, temp.length(),
queryStr.substring(i, j)).toString())) {
sub.add(temp.toString());
}
}
}
System.out.print(" " + sub.size());
sub.clear();
}// end of compute
}// end of class

14 پاسخ to “چطور مثل یک حرفه‌ای برنامه بنویسیم – بخش دوم: حوزه‌ی متغیرها”

  1. نگارنده کد مثال ! Says:

    سلامی دوباره ؛
    قبل از اینکه به برنامه ی خودم – و دلیلی که برای تعیین محل تعریف متغیرهام متصور بودم -بپردازم، چند سوال در خصوص حالات عنوان شده میپرسم (درست به ترتیبی که شما محتوای مطلبتون رو ساماندهی کردید)
    حالت یکم :
    گرچه مثالی که مطرح کردید مربوط به یک متغیر از نوع String هست و همونطور که میدونیم رشته ها از نوع اشیای تغییرناپذیر (immutable : یعنی سکبار که تعریف شد، رشته ی نتیجه ی حاصل از دستکاری رشته ی اصلی، شیءی جدید خاهد بود مستقل از شیء پیشین) هستند و از اینرو اگر من در موقعیت حل چنین مثالی بودم، حتمن در بکارگیری متغیرهای بیشتر (سه تا در این مثال) خسیس بازی در نمیاوردم !
    اما با در نظر گرفتن تمام اوصافی که در این بند فرمودید، بکارگیری چنین سیاستی آیا در کاهش میزان حافظه ی مصرفی (در صورت قابل تغییر بودن شیء مورد نظر) و حتی زمان محاسبه (چرا که زمانهای لازم برای نمونه سازی جدید و حتی – شاید – gc و یا برای روشن کردن منظورم : همون مکانیزمی که کامپایلر برای تشخیص فضایی از حافظه که دیگه استفاده ای نداره ،بکار میبره ، مشغول نخاهند شد) هیچ تاثیر یا تاثیر بسزایی نداره ؟

    آیا حالت اول رو به کل نهی میکنید ؟

    حالت سوم :
    آیا این حالت رو فقط و فقط به علت کاهش خانایی و مشکلات ناشی از آن نهی میکنید ؟
    ******************************************************************
    اما چرا من متغیری مثل temp که فقط در یک متد بخصوص از آن استفاده شده است !؟

    قبلش بگم خیلی خوشحالم که این بحث مطرح شده، چرا که مشابه چنین رویکردی رو – البته با تردید – برای چند برنامه ی دیگه هم در نظر گرفتم (اتفاقن عمده ی اونها هم به مسایل رمزنگاری میپردازند..) و فقط از سر انگیزه ی بالا بردن بازدهی و به دلیلی که هیچ ازش مطمئن نیستم و همیشه دلم میخاست فرصت و موقعیتی پیش بیاد تا از کسی که میدونه بپرسم.

    قبل از اینکه دلیلم رو به بحث و نقد بگذارم، باید بگم اگر شما اشتباهن در جمله ی زیر :
    «عمومی تعریف نکردن این متغیر داشته باشد زیرا از نظر من هیچ دلیلی وجود ندارد که این متغیر را به صورت محلی در تابع compute تعریف کرد. »
    از کلمات [عمومی] و [کرد] بجای کلمات [محلی] و [نکرد] استفاده کردید (چنانکه از بند آخر مقاله تون بر میاد) اونوقت میتونم توضیح بدم و گرنه – برای بنده – احتیاج به شفافسازی داره
    اما دلیل :
    فکر میکردم اگر در متدی که فقط اونجا از این متغیر (temp) استفاده شده (یعنی compute) دستور اعلان رو قرار بدم، با هر بار فراخانی متد، یکبار نمونه سازی وجود خاهد داشت طبیعتن و بعلاوه – بعد از مدتی – اشیای یتیم شده ی قبلی (از نوع ArraList) سربار حافظه میشند و باید کامپایلر مجبور به زباله روبی بشه؛

    بنابراین برای زدودن این هزینه، متغیر رو در حوزه ی کلاس تعریف کردم و در هر بار فراخانی متد – در انتهای کار – از clear استفاده کردم

    ———————————————————————————————————
    راستی درست بعد از فرستادن این پست، با راهنمایی ای که از شما بابت استفاده از ساختمان داده ی مناسب (Set) گرفتم ، قصد دارم جدیدترین نسخه ی همین برنامه رو پیاده سازی کنم که فکر میکنم بسیار کارا باشه …..
    نتیجه شو اعلام خاهم کرد
    ممنون از مطالب خوبتون

  2. adelist Says:

    اول از همه به خاطر گوشزد کردن اشتباه تایپی خیلی ممنون.
    واقعیت این است که من متوجه نمی‌شوم که immutable بودن یا نبودن یک شی چه خللی به این موضوع وارد می‌کند.
    هروقت که یک متغیر در جاوا تعریف شود (به غیر از وقتی که متغیر تعریف شده یکی از نوع‌های primitive type مثل int یا char یا … باشد) چهار بایت حافظه به آن اختصاص داده می‌شود. در این چهار بایت آدرس نقطه‌ای از حافظه که مقدار متغیر در آن ذخیره شده است نگهداری می‌شود.

    در مورد سوالی که پرسیده بودید، باید گفت در حالت کلی تصمیم‌گیری در این که از چه چیزهایی استفاده کنیم و از چه چیزهایی نه، بسیار دشوار است و در بسیاری از موارد حتی معیاری در دست نداریم که درستی تصمیمی که گرفته‌ایم را بسنجیم. ولی من یک قانون کلی برای تایید کردن یا رد کردن هر چیزی که ممکن است در کد شما ظاهر شود دارم که می‌تواند سودمند واقع شود:
    از هر چیزی که باعث می‌شود پیچیدگی کد زیادتر شود پرهیز کنید.
    در مورد حالت اول هم مساله همین است. به نظر می‌رسد که این حالت پیچیدگی برنامه را افزایش می‌دهد، اما اگر شما دلایل خوبی دارید که نشان می‌دهد این‌طور نیست، پس چرا نباید از این حالت استفاده کنید.
    اگر از یک برنامه‌نویس بپرسید که چرا نباید از دستور goto استفاده کرد جواب می‌دهد که استفاده از goto باعث می‌شود برنامه پیچیده شود. اما اگر در یک حالت خیلی خیلی خیلی خاص استفاده از این دستور باعث شود که پیچیدگی برنامه به طور چشم‌گیری کاهش پیدا کند، به نظر من در این صورت دلیلی وجود ندارد که از دستور goto استفاده نکنیم. اما حتی در این حالت هم در ابتدای فایلی که در آن از دستور goto استفاده شده است و همچنین در کنار خود دستور باید خیلی برجسته توضیح داد که در این قسمت به فلان دلیل از goto استفاده شده است. البته احتمالا از هر صدهزار برنامه‌نویس فقط یک نفر هم در کل زندگی خود با یک حالت این‌چنینی روبرو می‌شود

    اما در مورد متغیر temp:
    بهینه‌سازی‌های اینچنینی مانند آنچه شما در مورد temp انجام داده‌اید خیلی ساده و سرراست هستند. این بهینه‌سازی‌ها با دنبال کردن الگوریتم‌های خاص قابل تشخیص و اعمال هستند. و بسیاری از کامپایلرهای امروزی این بهینه‌سازی‌ها را برای شما انجام ‌می‌دهند. اگر سرعت اجرای کد شما تا این حد برای شما مهم است که به موضوعات این‌چنینی فکر می‌کنید پیشنهاد می‌کنم از یک کامپایلر قوی‌تر استفاده کنید و به جای فکر کردن به موضوعات این‌چنینی به مساله فکر کنید.

  3. نگارنده کد مثال ! Says:

    در مورد حالت اول به کلاس Point از کتابخانه ی awt جاوا توجه کنید و یک عملیات ترسیمی رو در نظر بگیرید ؛
    واضحه که نقطه ی اول و نقطه ی دوم و نقطه ی سوم (همان اشیای کلاس Point) از یکدیگر متمایز هستند، اما مطمئنن میدونید و قبول دارید که استفاده از یک متغیر (شیء) برای هر سه حالت و تغییر محتوای اونها (مختصات نقاط) بسیار بهینه تر هستند و اصولن به همین دلیل سازندگان این کلاس، متغیرهای x و y متعلق به این کلاس رو با دسترسی عمومی تعریف کردند.
    و این یک کلاس تغییرپذیر هست. از اینرو تعریف یک متغیر برای اون و دستکاری همون متغیر میتونه به صرفه تر باشه. (البته تا جای ممکن باید از پیچیدگی منطق و خانایی برنامه کاست، من هم تا جایی با این مسئله موافقم که لحاظ کردن این خانایی هزینه ی زیادی نداشته باشه، چنانکه از سرارسر نظراتم برمیاد)
    اما خب در مورد اشیای تغییرناپذیر (نظیر اشیای کلاس String) چنین چیزی صادق نیست. هر دستکاری در محتوای هر شیء این کلاس که یک خروجی String داشته باشه، شیء جدیدی ایجاد خاهد کرد. پس صرفه جویی ای وجود نخاهد داشت…

    در مورد برنامه ی مورد بحث :
    نگفتید تایید میفرمایید که این رویکرد در صرفه جویی هزینه ها موثر هست یا نه (فارغ از میزان این تاثیر گذاری)
    البته بنده هم با شما موافقم که چه بهتر ما همچنان که رویکردمون رو حرفه ای میکنیم، از ابزارهای حرفه ای(تر) هم استفاده کنیم.(ابزارهایی که من به لطف شما اولین بار با بعضیهاشون آشنا شدم و امیدوارم باز هم فرصتی داشته باشید تا یکبار درباره ی پارامترهای بهینه سازی و کامپایلرهای مختلف هم مطلبی بنویسید)
    اما در مورد برنامه ای که بنده نوشتم، قرار بود که یک کد منبع ارایه بشه و هیچ بهینه سازی کامپایلری هم دخیل نیست. اگر قرار بود که یک نرم افزار ارایه بشه، طبیعتن محصول ما از فایلهای کلاس تشکیل شده و این فایلها قبل از عرضه میتونند توسط کامپایلرهای قویتر بهینه بشند (اگر درست فهمیده باشم)

    حالا واقعن هنوز هم یک سوال صریح ذهنم رو به خودش مشغول داره :
    مثلن در مورد یک StringBuffer اگر در حوزه ی کلاس تعریف بشه و هربار درون متدی که فقط اونجا دستکاری میشه، و با استفاده از متد replace خودش محتواش رو تغییر بدیم بهتره، یا اینکه هر بار که قصد تغییر محتواشو داریم، با استفاده از دستور new مقدار جدید رو به پوینتر (در واقع متغیر ارجاع 32 بیتی-int- که شما بهش اشاره داشتید) منتسب کنیم ؟

    • adelist Says:

      اول از همه این نکته را متذکر می‌شوم که با وجود این‌که کتاب‌خانه‌ی جاوا
      خیلی خوب طراحی و پیاده‌سازی شده است، با این حال به هیچ وجه وحی منزل
      نیست. یک نگاه به این صفحه بیندازید تا خودتان ببینید که
      ابزار Findbugs چند تا اشکال در sun-jdk پیدا کرده است. اگر در یک کاربرد
      از کتابخانه‌ی awt نیاز است تا این اندازه به کارآیی اهمیت داده شود،‌
      می‌توانند تابع‌ها را به صورت inline کامپایل کنند.

      بعد دوباره یادآوری می‌کنم که کلید نوشتن برنامه‌های سریع در گرو نوشتن
      برنامه‌های خوب است. بهینه‌سازی‌های این‌چنینی (با این فرض که اثر منفی روی
      کارآیی ندارند) معمولا تاثیر محسوسی روی کارآیی ندارند.

      در مورد اشیا immutable به نظر می‌رسد که من و شما داریم در مورد دو موضوع
      متفاوت صحبت می‌کنیم. در این زمینه حق با شماست ولی من داشتم در مورد چیز
      دیگری حرف می‌زدم. از حوصله‌ی این توضیح است که فعلا به این موضوع
      بپردازیم.

      در مورد سوالی که در مورد صرفه جویی پرسیده‌اید (مثالی که من با سه رشته
      زدم) معتقدم که هیچ تاثیری ندارد، به این حتی دلیل که کامپایلرها این کارها
      را به جای شما انجام می‌دهند. حتی اگر تاثیری هم داشته باشد در مقایسه با
      زمان لازم برای اجرای برنامه بسیار ناچیز است.

      در مورد سوال StringBuffer هم باید به پیاده‌سازی متد replace و سازنده‌ی
      کلاس رجوع کنید. باید با یک پروفایلر اندازه‌گیری کنید و خودتان نتیجه را
      مقایسه کنید.

  4. حسن موبد Says:

    اسی پسر دمت گرم.
    همیشه بهت افتخار میکردم و حالا بیشتر.
    همیشه موفق باشی

    • نگارنده کد مثال ! Says:

      ممنون پسردایی جان
      البته این مساله که هنوز حل نشده ولی به لطف مولف این بلاگ اطلاعات ارزشمندی دارم کسب میکنم.
      از مولف بلاگ هم بابت این دو پیغام عذر میخام، میدونم که ممکنه اصلن دوست نداشته باشید به غیر از موضوع صحبت بشه… به همین خاطر چه بهتر که این دو نظر رو حذف کنید.

      در ضمن آقای موبد از حامیان من هستند و هر از گاهی اسممو + جاوا گوگل میکنند و …

      ارادتمند

  5. نگارنده کد مثال ! Says:

    ضمن اینکه با نظرتون در خصوص خالی از باگ نبودن کتابخونه ی استاندارد صددرصد موافقم و هر از گاهی، از گوشه کناری، در خود فرومهای SDN هم به صفحات پرشماری که گزارش این باگهاست، برخوردم و همچنین تولد کلاسها و APIهای جدید (و یا توسعه ی کلاسهای سابق) هم از برای رفع این ایرادات و موید همین ادعاست و این توسعه همچنان هم ادامه خاهد داشت…
    در مورد کامپایل inline هم چیزی نمیدونم و از شما انتظار ندارم تا خودم اطلاعاتی درباره ش کسب نکردم وقتتون رو بگیرم.

    قصد من زوم کردن روی مثالی که آوردید نبود و برای روشن کردن منظور مثال زدم (درست مثل شما) و میخاستم جنبه های مختلف رو بررسی کنیم.
    اما اتفاقن در خصوص کلاس Point کنجکاوم نظرتون رو درباره ی مسئله ای که مثال میزنم بدونم :
    public class EfficiencyCompare {

    public static void main(String[] args) {
    // specifies the number of iteration,the bigger intends to more realistic result
    int aBigNumber = 2000;
    new EfficiencyCompare().myApproach(aBigNumber);
    new EfficiencyCompare().javanerdApproach(aBigNumber);
    }

    void myApproach(int prototype) {
    java.awt.Point p = new java.awt.Point();
    for (int i = 0; i < prototype; i++, p.x++, p.y++) {
    System.out.println(p);
    }
    }

    void javanerdApproach(int prototype) {
    java.awt.Point p ;
    for (int i=0; i < prototype; i++){
    p = new java.awt.Point(i, i);
    System.out.println(p);
    }
    }
    }
    متاسفانه در این لحظه اون پروفایلری که به پیشنهادتون گرفته بودم رو فقط دارم (profiler4j) و باز هم خطا میده، میرم یه پروفایلر دیگه امتحان کنم.
    شما هم به هر حال میتونید تست کنید

  6. نگارنده کد مثال ! Says:

    یار در خانه و ما گرد جهان میگشتیم !
    دوست عزیز اصلن متوجه نبودم که خود محیط توسعه ای که بنده ازش استفاده میکنم، یعنی NetBeans، یه پروفایلر خیلی پیشرفته و ادغام شده درون خودش داره …
    من کلاسی که در پست قبل فرستادم رو به دو کلاس مجزا تجزیه کردم، تا شاید نتیجه ی مطمئنتری حاصل بشه.
    نتیجه ی آخرین تست رو با نمونه ی 5000 تکرار در صفحه ی زیر بارگزاری کردم:

    اما متاسفانه در طول چندین تست با نمونه های متفاوت، نتایج ضد و نقیضی بدست آوردم !
    گاهی رویکرد شما دوبرابر رویکرد من زمان میبرد و گاهی از مال من کمتر میشد !

    به یاد یکی از تجربیات شخصی خودم افتادم، زمانیکه بدون ابزار پروفایل کننده (با محاسبه ی زمان سیستم در شروع و پایان فراخانی متد) قصد مقایسه ی کارایی چند متد مختلف که یک کار واحد رو با الگوریتمهای متفاوت انجام میدادند، شدم :
    http://barnamenevis.org/forum/showthread.php?t=202273&goto=newpost

    نمیدونم با این حساب تا چه حد میشه به پروفایلینگ اعتماد کرد …

    • adelist Says:

      بسیار عالی. شاید دلیل اینکه زمان‌های اینقدر متغیر هستند این باشه که از دستورهای IO استفاده کردید (دستور println)

    • adelist Says:

      شما به از eclipse‌ برای کدنویسی استفاده کن تا من به شما صد تا از این ابزارها معرفی کنم.

  7. نگارنده کد مثال ! Says:

    راستی من همون موقع که شما – به درستی – گفتید دستورات ورودی/خروجی روی نتیجه ی ضد و نقیض آزمایش تاثیر میگذاره، دستور چاپ رو از متد حذف کردم و نمونه ی آزمایش رو 500000 تنظیم کردم و چند باری که تست گرفتم 4 ، 5 برابری هر بار اختلاف زمان داشتند متدهای من و شما و اینجا پی بردم جمله ای که تو کتاب رالف مورلی خوندم درست بوده:

    «فراخانی متد، سربار پردازشی بیشتریرا نسبت به دستیابی مستقیم (از من.به فیلدهای شیء) تحمیل میکند»

    نظر شما چیه ؟
    شرمنده فرصت نشد همون موقع بیام بگم…

  8. zahra Says:

    سلام ممنون از وبلاگ بسیار مفیدتون.
    لطفا من را در مورد یک پروفایلر خوب جاوا راهنمایی کنید. من یک کد به زبان جاوا نوشتم،ولی مشکل heap space دارد می خواهم بدانم به وسیله ی چه ابزاری می توانم تشخیص بدهم کدام قسمت برنامه این مشکل را دارد.لطفا هر چه سریعتر من را راهنمایی کنید.ممنون

    • Author Says:

      من شخصا از پلاگین TPTP توی اکلیپس استفاده می‌کنم. برای اینکه این پلاگین درست کار کنه لازمه که پلاگین BIRT نصب شده باشه. توی بقیه محیط های برنامه نویسی هم این امکانات وجود داره (حداقل توی نسخه‌های جدیدشون). اگر از netbeans استفاده می‌کنید یک نگاه به netbeans profiler بندازید.

      یک نگاه هم به این صفحه بندازید:
      http://java-source.net/open-source/profilers
      شاید چیزی پیدا کنید که به کار شما بیاد.

بیان دیدگاه