Sihirli sayı nedir?
Neden kaçınılmalıdır?
Uygun olduğu durumlar var mı?
Sihirli sayı nedir?
Neden kaçınılmalıdır?
Uygun olduğu durumlar var mı?
Yanıtlar:
Sihirli sayı, koddaki bir sayının doğrudan kullanımıdır.
Örneğin, (Java'da) varsa:
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
Bu, aşağıdakiler için yeniden düzenlenmelidir:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
Kodun okunabilirliğini artırır ve bakımı daha kolaydır. GUI'de şifre alanının boyutunu ayarladığım durumu düşünün. Sihirli bir sayı kullanırsam, maksimum boyut her değiştiğinde, iki kod konumunda değişmem gerekir. Birini unutursam, bu tutarsızlıklara yol açacaktır.
JDK in gibi örnekleriyle doludur Integer
, Character
ve Math
sınıflar.
Not: FindBugs ve PMD gibi statik analiz araçları, kodunuzda sihirli sayıların kullanımını algılar ve yeniden düzenlemeyi önerir.
TRUE
/ FALSE
)
Sihirli Sayı, daha sonraki bir aşamada değişebilen sabit kodlanmış bir değerdir, bu nedenle güncellenmesi zor olabilir.
Örneğin, "Siparişleriniz" Genel Bakış Sayfasında son 50 Siparişi görüntüleyen bir Sayfanız olduğunu varsayalım. Burada Sihirli Sayı 50'dir, çünkü standart veya konvansiyonla ayarlanmamıştır, spesifikasyonda belirtilen nedenlerle oluşturduğunuz bir sayıdır.
Şimdi, yaptığınız şey 50 farklı yerde - SQL komut dosyanız ( SELECT TOP 50 * FROM orders
), Web siteniz (Son 50 Siparişiniz), sipariş giriş bilgileriniz ( for (i = 0; i < 50; i++)
) ve muhtemelen birçok başka yerde.
Şimdi, birisi 50'den 25'e değişmeye karar verdiğinde ne olur? veya 75 mi? veya 153? Şimdi her yerde 50'yi değiştirmeniz gerekiyor ve bunu kaçırmanız çok muhtemel. Bul / Değiştir işe yaramayabilir, çünkü 50 başka şeyler için de kullanılabilir ve 50'yi 25 ile körü körüne değiştirmek bazı diğer kötü yan etkilere neden olabilir (örneğin, Session.Timeout = 50
25'e ayarlanan ve kullanıcılar çok sık zaman aşımı bildirmeye başlar).
Ayrıca, kodu anlamak zor olabilir, yani " if a < 50 then bla
" - karmaşık bir işlevin ortasında karşılaşırsanız, koda aşina olmayan diğer geliştiriciler kendilerine "WTF 50 mi ???"
Bu yüzden tam olarak 1 yerde bu tür belirsiz ve rasgele sayılara sahip olmak en iyisidir - " const int NumOrdersToDisplay = 50
", çünkü bu kodu daha okunaklı hale getirir (" if a < NumOrdersToDisplay
", aynı zamanda sadece 1 iyi tanımlanmış yerde değiştirmeniz gerekir.
Sihirli Sayıların uygun olduğu yerler, bir standart aracılığıyla tanımlanan her şeydir, yani SmtpClient.DefaultPort = 25
veya TCPPacketSize = whatever
(bunun standartlaştırılmış olup olmadığından emin değildir). Ayrıca, yalnızca 1 işlevde tanımlanan her şey kabul edilebilir, ancak bu Bağlama bağlıdır.
SmtpClient.DefaultPort = 25
muhtemelen açıktır er daha SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
uygulamanın her yerinde arama yapmanız ve 25
örneğin bir tablo sütununun genişliği veya sayısı olan 25'leri değil, yalnızca SMTP Bağlantı Noktası için olanları değiştirdiğinizden emin olmanız gerekir. sayfada gösterilecek kayıtların listesi.
IANA
.
Sihirli numara için Wikipedia girişine baktın mı?
Sihirli sayı referansının tüm yolları hakkında biraz ayrıntıya girer. İşte kötü bir programlama uygulaması olarak sihirli sayı hakkında bir alıntı
Sihirli sayı terimi, açıklama yapmadan sayıları doğrudan kaynak kodunda kullanmanın kötü programlama uygulamasına da işaret eder. Çoğu durumda bu, programların okunmasını, anlaşılmasını ve sürdürülmesini zorlaştırır. Çoğu kılavuz sıfır ve bir sayıları için bir istisna oluştursa da, koddaki diğer tüm sayıları adlandırılmış sabitler olarak tanımlamak iyi bir fikirdir.
Büyü: Bilinmeyen anlambilim
Sembolik Sabit -> Kullanım için hem doğru semantik hem de doğru bağlam sağlar
Anlamsal: Bir şeyin anlamı veya amacı.
"Bir sabit oluşturun, anlamdan sonra adlandırın ve sayıyı onunla değiştirin." - Martin Fowler
İlk olarak, sihirli sayılar sadece sayılar değildir. Herhangi bir temel değer "büyü" olabilir. Temel değerler, tamsayılar, gerçekler, çiftler, kayan noktalar, tarihler, dizeler, booleans, karakterler vb. Gibi tezahür varlıklardır. Sorun veri türü değil, kod metnimizde göründüğü gibi değerin "sihirli" yönüdür.
"Büyü" ile ne demek istiyoruz? Kesin olmak gerekirse: "Büyü" ile, kodumuz bağlamında değerin anlambilimine (anlam veya amaç) işaret etmeyi amaçlıyoruz; bilinmeyen, bilinemez, belirsiz veya kafa karıştırıcı. Bu "büyü" nosyonudur. Semantik anlamı veya var olma amacı, özel yardımcı kelimeler (örn. Sembolik sabit) olmadan çevre bağlamından hızlı ve kolay bir şekilde bilinir, anlaşılır ve anlaşılırsa (kafa karıştırıcı değildir) temel bir değer sihirli değildir.
Bu nedenle, bir kod okuyucunun çevredeki bağlamdan temel bir değerin anlamını ve amacını bilme, açık olma ve anlama yeteneğini ölçerek sihirli sayıları belirleriz. Okuyucu ne kadar az bilinirse, o kadar az anlaşılır ve kafan karışırsa, temel değer o kadar "büyü" olur.
Sihirli temel değerlerimiz için iki senaryomuz var. Programcılar ve kodlar için sadece ikincisi birincil öneme sahiptir:
"Sihir" in kapsayıcı bir bağımlılığı, yalnız temel değerin (örn. Sayı) yaygın olarak bilinen bir anlambilimine (Pi gibi) sahip olmadığı, ancak bağlamdan tamamen açık olmayan veya kötüye kullanılabilen yerel olarak bilinen bir anlambilime (ör. Programınız) sahip olmasıdır. iyi veya kötü bağlam (lar) da.
Çoğu programlama dilinin anlambilimi, (belki de) veri (yani veri tabloları) dışında yalnız temel değerleri kullanmamıza izin vermez. "Sihirli sayılar" ile karşılaştığımızda, bunu genellikle bir bağlam içinde yaparız. Bu nedenle,
"Bu sihirli sayıyı sembolik bir sabitle değiştirir miyim?"
dır-dir:
"Sayının anlamsal anlamını (orada olma amacı) bağlamında ne kadar hızlı değerlendirebilir ve anlayabilirsiniz?"
Bu düşünce göz önünde bulundurularak, Pi (3.14159) gibi bir sayının uygun bağlamda yerleştirildiğinde nasıl "sihirli sayı" olmadığını hızlı bir şekilde görebiliriz (örneğin, 2 x 3.14159 x yarıçapı veya 2 * Pi * r). Burada, 3.14159 sayısı, sembolik sabit tanımlayıcı olmadan zihinsel olarak tanınan Pi'dir.
Yine de, genellikle 3.14159'u sayının uzunluğu ve karmaşıklığı nedeniyle Pi gibi sembolik bir sabit tanımlayıcı ile değiştiriyoruz. Pi'nin uzunluk ve karmaşıklık yönleri (doğruluk ihtiyacı ile birleştiğinde) genellikle sembolik tanımlayıcı veya sabitin hataya daha az eğilimli olduğu anlamına gelir. "Pi" nin bir isim olarak tanınması sadece uygun bir bonusdur, fakat sabite sahip olmanın birincil nedeni değildir.
Pi gibi ortak sabitleri bir kenara bırakarak, öncelikle özel anlamları olan sayılara odaklanalım, ancak bu anlamların yazılım sistemimizin evreniyle kısıtlandığı. Böyle bir sayı "2" olabilir (temel bir tamsayı değeri olarak).
2 sayısını tek başına kullanırsam, ilk sorum şu olabilir: "2" ne anlama geliyor? "2" nin anlamı tek başına bilinmeyen ve bilinmeyen, kullanımı belirsiz ve kafa karıştırıcıdır. Yazılımımızda sadece "2" olması dil semantiği nedeniyle gerçekleşmeyecek olsa da, "2" nin tek başına özel bir anlambilim veya açık bir amaç taşımadığını görmek istiyoruz.
Yalnız "2" yi şu içeriğe koyalım: padding := 2
burada bağlam "GUI Container" dır. Bu bağlamda 2'nin anlamı (piksel veya başka bir grafik birim olarak) bize anlambilimi (anlam ve amaç) hakkında hızlı bir tahminde bulunur. Burada durabilir ve bu bağlamda 2'nin iyi olduğunu ve bilmemiz gereken başka bir şey olmadığını söyleyebiliriz. Ancak, belki de yazılım evrenimizde bu hikayenin tamamı değildir. Daha fazlası var, ama bir bağlam olarak "padding = 2" bunu ortaya çıkaramaz.
Programımızdaki piksel dolgusu olarak 2'nin sistemimizdeki "default_padding" çeşidinde olduğunu varsayalım. Bu nedenle, talimatı yazmak padding = 2
yeterince iyi değildir. "Varsayılan" kavramı ortaya çıkmaz. Sadece yazdığım zaman: padding = default_padding
bir bağlam olarak ve sonra başka bir yerde: default_padding = 2
sistemimizde 2'nin daha iyi ve daha dolgun bir anlamını (anlamsal ve amaç) tam olarak anlıyorum.
Yukarıdaki örnek oldukça iyi çünkü tek başına "2" herhangi bir şey olabilir. Anlayış aralığını ve etki alanını yalnızca "programım" default_padding
ın GUI UX bölümlerinde 2 olan "programım" ile sınırladığımızda, nihayet uygun bağlamda "2" yi anlamlandırırız. Burada "2", "kodum" default_padding
un GUI UX default_padding
kapsamı içinde, çevreleyen kodun daha büyük bağlamında hızlı bir şekilde anlaşılmasını sağlamak için, sembolik bir sabit olarak hesaplanan bir "sihirli" sayıdır .
Dolayısıyla, anlamı (anlamsal ve amaç) yeterince ve hızlı bir şekilde anlaşılamayan herhangi bir temel değer, temel değerin (örn. Sihirli sayı) yerine sembolik bir sabit için iyi bir adaydır.
Ölçekteki sayılar semantik de olabilir. Örneğin, bir canavar kavramına sahip olduğumuz bir D&D oyunu yapıyoruz gibi davranın. Canavar nesnemiz, life_force
tamsayı olarak adlandırılan bir özelliğe sahiptir . Sayılar, anlam sağlayacak sözcükler olmadan bilinemez veya net olmayan anlamlara sahiptir. Böylece, keyfi olarak şunu söyleyerek başlarız:
Yukarıdaki sembolik sabitlerden, D & D oyunumuzdaki canavarlarımız için canlılık, ölüm ve "ölümsüzlük" (ve olası sonuçlar) hakkında zihinsel bir resim elde etmeye başlarız. Bu kelimeler olmadan (sembolik sabitler), sadece sayılar arasında kalırız -10 .. 10
. Sadece kelimelerin olmadığı aralık bizi oyunun farklı bölümlerinin attack_elves
veya gibi çeşitli işlemlere ne anlama geldiğine bağlıysa, muhtemelen büyük karışıklık ve potansiyel olarak oyunumuzdaki hatalarla dolu bir yerde bırakır seek_magic_healing_potion
.
Bu nedenle, "sihirli sayıları" ararken ve değiştirirken, yazılımımızın kapsamındaki sayılar ve hatta sayıların birbiriyle anlamsal olarak nasıl etkileşime girdiğiyle ilgili çok amaçlı sorular sormak istiyoruz.
Hangi soruları sormamız gerektiğini gözden geçirelim:
Sihirli bir numaranız olabilir ...
Kod metninizde tek başına manifest sabit temel değerleri inceleyin. Her bir soruyu böyle bir değerin her bir örneği hakkında yavaş ve düşünceli bir şekilde sorun. Cevabınızın gücünü düşünün. Çoğu zaman, cevap siyah ve beyaz değildir, ancak yanlış anlaşılmış anlam ve amaç, öğrenme hızı ve anlama hızı tonlarına sahiptir. Ayrıca, etrafındaki yazılım makinesine nasıl bağlandığını da görmek gerekir.
Sonunda, değiştirmenin cevabı, bağlantıyı yapmak için okuyucunun kuvvetini veya zayıflığını ölçmek (zihninizde) (örn. "Anlayın"). Anlam ve amacı ne kadar çabuk anlarlarsa, o kadar az "sihir" elde edersiniz.
SONUÇ: Temel değerleri yalnızca sihir, karışıklıklardan kaynaklanan hataları tespit etmek zor olacak kadar büyük olduğunda sembolik sabitlerle değiştirin.
Sihirli sayı, bir dosya biçiminin veya protokol değişiminin başlangıcındaki karakter dizisidir. Bu numara bir sağlık kontrolü olarak işlev görür.
Örnek: Herhangi bir GIF dosyasını açın, en başta göreceksiniz: GIF89. "GIF89" sihirli sayıdır.
Diğer programlar bir dosyanın ilk birkaç karakterini okuyabilir ve GIF'leri düzgün bir şekilde tanımlayabilir.
Tehlike, rastgele ikili verilerin aynı karakterleri içerebilmesidir. Ancak bu pek olası değildir.
Protokol değişimine gelince, size iletilmekte olan geçerli 'mesajın' bozuk veya geçersiz olduğunu hızlı bir şekilde tanımlamak için kullanabilirsiniz.
Sihirli sayılar hala faydalıdır.
Programlamada, bir "sihirli sayı" sembolik bir ad verilmesi gereken bir değerdir, ancak bunun yerine koda değişmez olarak, genellikle birden fazla yerde yerleştirilir.
SPOT'un (Tek Doğruluk Noktası) iyi olmasının nedeni de kötü: Bu sabiti daha sonra değiştirmek isterseniz, her örneği bulmak için kodunuzu araştırmanız gerekir. Aynı zamanda kötüdür, çünkü bu sayının neyi temsil ettiği diğer programcılara, dolayısıyla "büyüye" açık olmayabilir.
İnsanlar bazen bu sabitleri yapılandırma olarak hareket etmek için ayrı dosyalara taşıyarak sihirli sayıları yok ederler. Bu bazen yararlıdır, ancak değerinden daha fazla karmaşıklık da yaratabilir.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
bir döngüden çok daha hızlı değerlendirilebilir. Eğer 3
kod bir döngü olarak yeniden yazılmadan değiştirilecek olsaydı , ITEMS_TO_AVERAGE
tanımlı olarak gören kişi kodu 3
değiştirebileceğini 5
ve kod ortalamasının daha fazla öğeye sahip olabileceğini düşünebilirdi . Bunun aksine, ifadeye gerçek kelimeyle bakan biri, birlikte toplanan öğelerin sayısını temsil ettiğini 3
fark 3
eder.
Sihirli bir sayı, özel, sabit kodlu semantik içeren bir sayı da olabilir. Örneğin, bir zamanlar> 0 kayıt kimliklerinin normal olarak işlendiği, 0'ın kendisinin "yeni kayıt" olduğu, -1'in "bu kök olduğu" ve -99'un "kökte oluşturulduğu" bir sistem gördüm. 0 ve -99, WebService'in yeni bir kimlik sağlamasına neden olur.
Bunun kötü yanı, bir alanı (kayıt kimlikleri için imzalı tamsayıların) özel yetenekler için yeniden kullanmanızdır. Belki hiçbir zaman 0 kimliği veya negatif kimliği olan bir kayıt oluşturmak istemezsiniz, ancak olmasa bile, koda veya veritabanına bakan herkes bununla karşılaşabilir ve ilk başta karıştırılabilir. Bu özel değerlerin iyi belgelenmediğini söylemeye gerek yok.
Muhtemelen 22, 7, -12 ve 620 de sihirli sayılar olarak sayılır. ;-)
Sihirli sayılar kullanıldığında bahsedilmeyen bir sorun ...
Çok fazla varsa, sihirli sayıları kullandığınız, değerlerin aynı olduğu iki farklı amaca sahip olma ihtimaliniz oldukça iyidir .
Ve sonra, yeterince, değeri değiştirmeniz gerekiyor ... sadece bir amaç için.
Bunun önceki sorunuza vermiş olduğum cevaba bir yanıt olduğunu varsayıyorum . Programlamada sihirli bir sayı, açıklama yapmadan görünen gömülü bir sayısal sabittir. İki farklı yerde görünürse, bir örneğin değiştirildiği ve başka bir örneğin değiştirildiği koşullara yol açabilir. Bu iki nedenden ötürü, sayısal sabitleri kullanıldıkları yerlerin dışında izole etmek ve tanımlamak önemlidir.
Her zaman hızlı bir geçerlilik kontrolü olarak doğrulanabilen bir veri yapısı içinde saklanan belirsiz bir değer olarak "sihirli sayı" terimini farklı şekilde kullandım. Örneğin, gzip dosyaları ilk üç baytı 0x1f8b08 içerir, Java sınıfı dosyaları 0xcafebabe, vb. İle başlar.
Sıklıkla dosya formatlarında gömülü sihirli sayılar görürsünüz, çünkü dosyalar oldukça karışık bir şekilde gönderilebilir ve bunların nasıl oluşturulduğuyla ilgili meta verileri kaybedebilir. Ancak sihirli sayılar bazen ioctl () çağrıları gibi bellek içi veri yapıları için de kullanılır.
Dosya veya veri yapısını işlemeden önce sihirli sayının hızlı bir şekilde kontrol edilmesi, girişin tam bir balderdash olduğunu duyurmak için potansiyel olarak uzun işlemlerle tamamen hata yapmak yerine, hataları erkenden bildirmesini sağlar.
Bazen kodunuzda yapılandırılamayan "sabit kodlanmış" numaralar istediğinizi belirtmek gerekir. Optimize edilmiş ters kare kök algoritmasında kullanılan 0x5F3759DF dahil olmak üzere bir dizi ünlü var .
Bu tür Sihirli Numaraları kullanma ihtiyacını bulduğum nadir durumlarda, kodumda bir sabit olarak ayarladım ve neden kullanıldıklarını, nasıl çalıştıklarını ve nereden geldiklerini belgeledim.
Sınıfın en üstünde bir değişkeni varsayılan bir değerle başlatmaya ne dersiniz? Örneğin:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
Bu durumda, 15000 sihirli bir sayıdır (CheckStyles'e göre). Bana göre, varsayılan bir değer ayarlamak iyidir. Yapmak istemiyorum:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
Bu okumayı zorlaştırıyor mu? CheckStyles'i yükleyene kadar bunu hiç düşünmedim.
static final
bunları tek bir yöntemde kullandığınızda sabitler aşırıya kaçıyor. final
Yöntemin üstünde bildirilen bir değişken daha okunabilir IMHO'dur.
@ eed3si9n: '1'in sihirli bir sayı olduğunu bile söyleyebilirim. :-)
Sihirli sayılarla ilgili bir ilke, kodunuzun ele aldığı her gerçeğin tam olarak bir kez beyan edilmesi gerektiğidir. Kodunuzda sihirli sayılar kullanırsanız (@marcio'nun verdiği şifre uzunluğu örneği gibi, bu gerçeği kolayca çoğaltabilirsiniz ve bu gerçeği anladığınız zaman bir bakım sorununuz vardır.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
Dönüş değişkenleri ne olacak?
Saklı yordamları uygularken özellikle zorlayıcı buluyorum .
Bir sonraki saklı yordamı düşünün (yanlış bir sözdizimi, biliyorum, sadece bir örnek göstermek için):
int procGetIdCompanyByName(string companyName);
Belirli bir tabloda varsa şirketin kimliğini döndürür. Aksi takdirde -1 döndürür. Her nasılsa sihirli bir sayı. Şimdiye kadar okuduğum önerilerden bazıları, böyle bir şey yapmak zorunda kalacağımı söylüyor:
int procGetIdCompanyByName(string companyName, bool existsCompany);
Bu arada, şirket yoksa ne dönmeli? Tamam: existesCompany değerini yanlış olarak ayarlar , ancak -1 döndürür.
Antoher seçeneği iki ayrı işlev yapmaktır:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Dolayısıyla, ikinci saklı yordam için bir ön koşul şirketin mevcut olmasıdır.
Ama eşzamanlılıktan korkuyorum, çünkü bu sistemde bir şirket başka bir kullanıcı tarafından oluşturulabilir.
Bu arada sonuç olarak: bir şeyin başarısız olduğunu veya bir şeyin mevcut olmadığını söylemek için nispeten bilinen ve güvenli olan bu tür "sihirli sayılar" kullanmak hakkında ne düşünüyorsunuz?
Bir sihirli sayıyı sabit olarak çıkarmanın bir diğer avantajı, işletme bilgilerini açıkça belgeleme imkanı verir.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
şu an 11'im insanlar ya da bira şişeleri veya başka bir şey olabilir, bunun yerine 11'i sabit olarak değiştiririm sakinleri gibi.